3854 lines
142 KiB
Dart
Executable File
3854 lines
142 KiB
Dart
Executable File
import 'package:csv/csv.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:nhancepolicy/customAppBar/customAppBar.dart';
|
|
import 'package:nhancepolicy/presentation/policies.dart';
|
|
import 'package:nhancepolicy/presentation/preFileUpload.dart';
|
|
import 'package:nhancepolicy/presentation/postFileUpload.dart';
|
|
import 'package:nhancepolicy/service/api_service.dart';
|
|
import 'package:nhancepolicy/presentation/claims.dart';
|
|
import 'package:jwt_decode/jwt_decode.dart';
|
|
import 'dart:convert';
|
|
import 'dart:async';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
|
import 'package:nhancepolicy/service/token_storage_service.dart';
|
|
import 'package:universal_html/html.dart' as html;
|
|
import 'dart:typed_data';
|
|
import 'dart:io';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import 'package:flutter/services.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:collection/collection.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../customAppBar/base_layout.dart';
|
|
import '../customAppBar/customFooter.dart';
|
|
import '../service/secure_pop_scope.dart';
|
|
import 'package:nhancepolicy/presentation/email_template/email_template_models.dart';
|
|
import 'package:nhancepolicy/presentation/email_template/reminder_email_template_dialog.dart';
|
|
import 'package:nhancepolicy/logger.dart';
|
|
|
|
class hrPolicyDetails extends StatefulWidget {
|
|
final String ClientId;
|
|
final String policyTypeId;
|
|
// final String ClientBranchId;
|
|
final String ClientPoliyId;
|
|
final String clientBranchId;
|
|
final String Token;
|
|
final String TokenType;
|
|
final String cardType;
|
|
final String cardPolicyNo;
|
|
final String cardInsurer_name;
|
|
final String cardPolicy_name;
|
|
final String cardPolicy_ExpDate;
|
|
final String total_premium;
|
|
final int is_ecard_bulk_download_for_employee;
|
|
|
|
const hrPolicyDetails(
|
|
{Key? key,
|
|
required this.ClientId,
|
|
required this.policyTypeId,
|
|
// required this.ClientBranchId,
|
|
required this.ClientPoliyId,
|
|
required this.clientBranchId,
|
|
required this.Token,
|
|
required this.TokenType,
|
|
required this.cardType,
|
|
required this.cardPolicyNo,
|
|
required this.cardInsurer_name,
|
|
required this.cardPolicy_name,
|
|
required this.cardPolicy_ExpDate,
|
|
required this.total_premium,
|
|
required this.is_ecard_bulk_download_for_employee})
|
|
: super(key: key);
|
|
|
|
@override
|
|
State<hrPolicyDetails> createState() => _HrPolicyDetailsState();
|
|
}
|
|
|
|
class _HrPolicyDetailsState extends State<hrPolicyDetails>
|
|
with TickerProviderStateMixin {
|
|
String? localClientId;
|
|
String? localPolicyTypeId;
|
|
String? localClientPolicyId;
|
|
String? localClientBranchId;
|
|
String? localToken;
|
|
String? localTokenType;
|
|
String? localCardType;
|
|
String? localCardPolicyNo;
|
|
String? localCardInsurerName;
|
|
String? localCardPolicyName;
|
|
String? localCardPolicyExpDate;
|
|
String? localTotalPremium;
|
|
int localIsEcardBulkDownload = 0;
|
|
|
|
final tokenService = TokenStorageService();
|
|
dynamic empPrimaryId;
|
|
dynamic empClientId;
|
|
dynamic empClientBranchId;
|
|
dynamic empHrId;
|
|
dynamic enrollmentClient_id;
|
|
dynamic enrollmentEmpClientBranchId;
|
|
dynamic enrollmentHrId;
|
|
|
|
String? _postPreToken = '';
|
|
Uint8List? fileBytes;
|
|
String empCodeFromHrPolcy = '';
|
|
bool hasAnyEcardLink = false;
|
|
late String _token;
|
|
List<Map<String, dynamic>> getEmpDependenceByClintId = [];
|
|
List<Map<String, dynamic>> getCDPolicies = [];
|
|
bool isLoading = false;
|
|
bool _isLoading = false;
|
|
bool _isSendingReminder = false;
|
|
bool _isLoadingPolicyTerms = false;
|
|
// dynamic clintID;
|
|
late TabController _tabController;
|
|
// List<dynamic> dataPolicy = [];
|
|
List<dynamic> reversedDataPolicy = [];
|
|
List<Map<String, dynamic>> originalData = []; // Original data source
|
|
List<Map<String, dynamic>> filteredData = []; // Filtered data source
|
|
|
|
dynamic argumentsData;
|
|
dynamic policyType;
|
|
dynamic policyName;
|
|
dynamic clientPolicyId;
|
|
dynamic clientId;
|
|
dynamic empRefId;
|
|
String? modulesString;
|
|
final storeModuleId = 4;
|
|
bool hasModule = false;
|
|
int inceptionType = 0;
|
|
TextEditingController searchController = TextEditingController();
|
|
late ApiService apiService;
|
|
|
|
int _currentPage = 1;
|
|
int _rowsPerPage = 5;
|
|
|
|
/// Pre-enrollment status chip filter (null = show all).
|
|
String? _preStatusFilter;
|
|
|
|
/// holds selected employee ids as String or int (be consistent)
|
|
final Set<String> selectedEmployeeIds = {};
|
|
|
|
/// ids shown in the current page (for header checkbox)
|
|
List<String> currentPageIds = [];
|
|
|
|
List<dynamic> get _paginatedData {
|
|
final total = filteredData.length;
|
|
if (total == 0) return [];
|
|
|
|
final startIndex = ((_currentPage - 1) * _rowsPerPage).clamp(0, total);
|
|
final endIndex = (_currentPage * _rowsPerPage).clamp(0, total);
|
|
|
|
if (startIndex >= endIndex) return [];
|
|
return filteredData.sublist(startIndex, endIndex);
|
|
}
|
|
|
|
Color getStatusColor(String status) {
|
|
switch (status.toLowerCase()) {
|
|
case 'draft':
|
|
return const Color(0xFFF9EBBD);
|
|
case 'enrolled':
|
|
return const Color(0xFFBDF9D9);
|
|
case 'total':
|
|
return const Color(0xFFE2FBCB);
|
|
case 'under process':
|
|
return Color(0xFFBDF9D9);
|
|
case 'active':
|
|
return Colors.green;
|
|
case 'inactive':
|
|
return Colors.red;
|
|
default:
|
|
return const Color(0xFFB0BEC5);
|
|
}
|
|
}
|
|
|
|
//
|
|
// @override
|
|
// void initState() {
|
|
// super.initState();
|
|
// apiService = ApiService(context); // Initialize ApiService here
|
|
//
|
|
// logDebug("_PreEnrollmentState 1");
|
|
// getCDPoliciesDetails();
|
|
// logDebug('allowed_modules');
|
|
// }
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
apiService = ApiService(context);
|
|
// If you are using TabBar, you MUST initialize this:
|
|
_tabController = TabController(length: 2, vsync: this);
|
|
restorePolicyData();
|
|
}
|
|
|
|
Future<void> restorePolicyData() async {
|
|
localClientId = widget.ClientId.isNotEmpty
|
|
? widget.ClientId
|
|
: await tokenService.readValue('hr_ClientId');
|
|
|
|
localPolicyTypeId = widget.policyTypeId.isNotEmpty
|
|
? widget.policyTypeId
|
|
: await tokenService.readValue('hr_policyTypeId');
|
|
|
|
localClientPolicyId = widget.ClientPoliyId.isNotEmpty
|
|
? widget.ClientPoliyId
|
|
: await tokenService.readValue('hr_ClientPoliyId');
|
|
|
|
localClientBranchId = widget.clientBranchId.isNotEmpty
|
|
? widget.clientBranchId
|
|
: await tokenService.readValue('hr_clientBranchId');
|
|
|
|
localToken = widget.Token.isNotEmpty
|
|
? widget.Token
|
|
: await tokenService.readValue('hr_Token');
|
|
|
|
localTokenType = widget.TokenType.isNotEmpty
|
|
? widget.TokenType
|
|
: await tokenService.readValue('hr_TokenType');
|
|
|
|
localCardType = widget.cardType.isNotEmpty
|
|
? widget.cardType
|
|
: await tokenService.readValue('hr_cardType');
|
|
|
|
localCardPolicyNo = widget.cardPolicyNo.isNotEmpty
|
|
? widget.cardPolicyNo
|
|
: await tokenService.readValue('hr_cardPolicyNo');
|
|
|
|
localCardInsurerName = widget.cardInsurer_name.isNotEmpty
|
|
? widget.cardInsurer_name
|
|
: await tokenService.readValue('hr_cardInsurer_name');
|
|
|
|
localCardPolicyName = widget.cardPolicy_name.isNotEmpty
|
|
? widget.cardPolicy_name
|
|
: await tokenService.readValue('hr_cardPolicy_name');
|
|
|
|
localCardPolicyExpDate = widget.cardPolicy_ExpDate.isNotEmpty
|
|
? widget.cardPolicy_ExpDate
|
|
: await tokenService.readValue('hr_cardPolicy_ExpDate');
|
|
|
|
localTotalPremium = widget.total_premium.isNotEmpty
|
|
? widget.total_premium
|
|
: await tokenService.readValue('hr_total_premium');
|
|
|
|
final savedBulk =
|
|
await tokenService.readValue('hr_is_ecard_bulk_download_for_employee');
|
|
|
|
localIsEcardBulkDownload = widget.is_ecard_bulk_download_for_employee != 0
|
|
? widget.is_ecard_bulk_download_for_employee
|
|
: int.tryParse(savedBulk ?? '0') ?? 0;
|
|
|
|
getCDPoliciesDetails();
|
|
}
|
|
|
|
Future<void> clearPolicyStorage() async {
|
|
await tokenService.removeValue('hr_ClientId');
|
|
await tokenService.removeValue('hr_policyTypeId');
|
|
await tokenService.removeValue('hr_ClientPoliyId');
|
|
await tokenService.removeValue('hr_clientBranchId');
|
|
await tokenService.removeValue('hr_Token');
|
|
await tokenService.removeValue('hr_TokenType');
|
|
await tokenService.removeValue('hr_cardType');
|
|
await tokenService.removeValue('hr_cardPolicyNo');
|
|
await tokenService.removeValue('hr_cardInsurer_name');
|
|
await tokenService.removeValue('hr_cardPolicy_name');
|
|
await tokenService.removeValue('hr_cardPolicy_ExpDate');
|
|
await tokenService.removeValue('hr_total_premium');
|
|
await tokenService.removeValue('hr_is_ecard_bulk_download_for_employee');
|
|
}
|
|
|
|
// Future<void> _loadToken() async {
|
|
// _postPreToken = tokenService.getCurrentToken();
|
|
// if(localTokenType == "post") {
|
|
// empClientId = await tokenService.readValue('empClientId');
|
|
// empClientBranchId = await tokenService.readValue('empClientBranchId');
|
|
// empHrId = await tokenService.readValue('empHrId');
|
|
// }
|
|
// if(localTokenType == "pre") {
|
|
// enrollmentClient_id = await tokenService.readValue('enrollmentClient_id');
|
|
// enrollmentEmpClientBranchId = await tokenService.readValue('enrollmentEmpClientBranchId');
|
|
// enrollmentHrId = await tokenService.readValue('enrollmentHrId');
|
|
// }
|
|
//
|
|
// getCDPoliciesDetails();
|
|
// }
|
|
|
|
Future<void> getCDPoliciesDetails_06FEB() async {
|
|
logDebug('9');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
try {
|
|
logDebug('10');
|
|
|
|
modulesString = await tokenService.readValue('empAllowed_modules');
|
|
// modulesString = "[2,3]";
|
|
|
|
logDebug("empmodulesString - $modulesString");
|
|
|
|
if (modulesString != null && modulesString!.trim().isNotEmpty) {
|
|
final List<int>? moduleList = modulesString
|
|
?.replaceAll('[', '')
|
|
.replaceAll(']', '')
|
|
.split(',')
|
|
.map((e) => int.tryParse(e.trim()) ?? -1) // convert to int safely
|
|
.where((id) => id != -1) // filter out invalid
|
|
.toList();
|
|
|
|
hasModule = moduleList!.contains(storeModuleId);
|
|
}
|
|
|
|
final response = localTokenType == "post"
|
|
? await apiService.getEmployeeAndDependenceToApi(
|
|
localClientId ?? widget.ClientId,
|
|
localClientPolicyId ?? widget.ClientPoliyId,
|
|
localClientBranchId ?? widget.clientBranchId,
|
|
localToken ?? widget.Token)
|
|
: await apiService.getEmployeeAndDependenceToApiPre(
|
|
localClientId ?? widget.ClientId,
|
|
localClientPolicyId ?? widget.ClientPoliyId,
|
|
localClientBranchId ?? widget.clientBranchId,
|
|
localToken ?? widget.Token!);
|
|
|
|
if (response['status'] == 'success') {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
setState(() {
|
|
getCDPolicies = List<Map<String, dynamic>>.from(response['data']);
|
|
originalData = getCDPolicies;
|
|
_refreshFilteredData();
|
|
logDebug('filteredData');
|
|
logDebug(filteredData);
|
|
});
|
|
} else {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
|
|
// ToastHelper.showWarningToast(
|
|
// context, 'Request failed with status: ${response.statusCode}');
|
|
// logDebug('Request failed with status: ${response['code']}');
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
logDebug('Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> getCDPoliciesDetails() async {
|
|
logDebug('getCDPoliciesDetails started');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
logDebug('Fetching modules...');
|
|
modulesString = await tokenService.readValue('empAllowed_modules');
|
|
logDebug("empmodulesString - $modulesString");
|
|
|
|
if (modulesString != null && modulesString!.trim().isNotEmpty) {
|
|
final List<int>? moduleList = modulesString
|
|
?.replaceAll('[', '')
|
|
.replaceAll(']', '')
|
|
.split(',')
|
|
.map((e) => int.tryParse(e.trim()) ?? -1)
|
|
.where((id) => id != -1)
|
|
.toList();
|
|
|
|
hasModule = moduleList?.contains(storeModuleId) ?? false;
|
|
}
|
|
|
|
logDebug('Calling API with TokenType: ${localTokenType}');
|
|
logDebug(
|
|
'ClientId: ${localClientId}, ClientPoliyId: ${localClientPolicyId}');
|
|
|
|
final response = localTokenType == "post"
|
|
? await apiService.getEmployeeAndDependenceToApi(
|
|
localClientId ?? widget.ClientId,
|
|
localClientPolicyId ?? widget.ClientPoliyId,
|
|
localClientBranchId ?? widget.clientBranchId,
|
|
localToken ?? widget.Token)
|
|
: await apiService.getEmployeeAndDependenceToApiPre(
|
|
localClientId ?? widget.ClientId,
|
|
localClientPolicyId ?? widget.ClientPoliyId,
|
|
localClientBranchId ?? widget.clientBranchId,
|
|
localToken ?? widget.Token!);
|
|
|
|
logDebug('API Response: ${response.toString()}');
|
|
|
|
if (response != null && response['status'] == 'success') {
|
|
final data = response['data'];
|
|
if (data != null && data is List) {
|
|
// Check if it's actually a list
|
|
setState(() {
|
|
getCDPolicies = List<Map<String, dynamic>>.from(data);
|
|
originalData = getCDPolicies;
|
|
_refreshFilteredData();
|
|
isLoading = false;
|
|
});
|
|
} else {
|
|
setState(() => isLoading = false);
|
|
}
|
|
} else {
|
|
final errorCode = response?['code'] ?? 'Unknown';
|
|
// final errorMessage = response?['message'] ?? 'Request failed';
|
|
// logDebug('❌ Request failed - Code: $errorCode, Message: $errorMessage');
|
|
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
|
|
if (mounted) {
|
|
// ToastHelper.showErrorToast(context, errorMessage);
|
|
}
|
|
}
|
|
} catch (e, stackTrace) {
|
|
logDebug('❌ Exception occurred: $e');
|
|
logDebug('Stack trace: $stackTrace');
|
|
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(context,
|
|
'Failed to load data. Please check your connection and try again.');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> getEcardDownload(String? empCode, String? empId,
|
|
String? clientPolicyId, String? policyNo) async {
|
|
final eCarDParams = {
|
|
'id': empId,
|
|
'emp_code': empCode,
|
|
'client_policy_id': clientPolicyId,
|
|
'policy_no': policyNo
|
|
};
|
|
final response = await apiService.getEcardRequest(eCarDParams, localToken!);
|
|
logDebug('check 1');
|
|
final ecardDownloadUrl = response['data']['eCardDownload'];
|
|
final message = response['data']['message'];
|
|
if (ecardDownloadUrl != null) {
|
|
logDebug('✅ Link: $ecardDownloadUrl');
|
|
await _launchURL(ecardDownloadUrl); // Only launch if status is success
|
|
// ToastHelper.showSuccessToast(context, message);
|
|
} else {
|
|
logDebug('❌ Error: $message');
|
|
ToastHelper.showErrorToast(context, message);
|
|
}
|
|
}
|
|
|
|
Future<void> copyEcardLink(String? ecardLink) async {
|
|
if (ecardLink == null || ecardLink.trim().isEmpty) {
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(context, 'E-card link not available');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (kIsWeb) {
|
|
await html.window.navigator.clipboard?.writeText(ecardLink);
|
|
} else {
|
|
await Clipboard.setData(ClipboardData(text: ecardLink));
|
|
}
|
|
if (mounted) {
|
|
ToastHelper.showSuccessToast(context, 'Link copied to clipboard');
|
|
}
|
|
}
|
|
|
|
Future<void> sendEcardViaEmail(
|
|
dynamic empPolicyId,
|
|
dynamic clientPolicyId,
|
|
) async {
|
|
try {
|
|
final response = await apiService.sendMailForIndividualEmployeeEcard(
|
|
empPolicyId: empPolicyId,
|
|
clientPolicyId: clientPolicyId,
|
|
token: localToken!,
|
|
);
|
|
if (!mounted) return;
|
|
if (response['status'] == true) {
|
|
ToastHelper.showSuccessToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Mail sent successfully',
|
|
);
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Failed to send mail.',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logDebug('❌ sendEcardViaEmail error: $e');
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Failed to send mail. Please try again.',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _launchURL(String url) async {
|
|
final Uri uri = Uri.parse(url); // Parse the URL properly
|
|
logDebug('_launchURL $uri');
|
|
logDebug('If $uri');
|
|
if (kIsWeb) {
|
|
// Open in current browser tab on web.
|
|
await launchUrl(uri, webOnlyWindowName: '_self');
|
|
} else {
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
}
|
|
}
|
|
|
|
bool _isEnrolledStatus(String status) =>
|
|
status == 'under process' ||
|
|
status == 'enrolled' ||
|
|
status == 'submitted';
|
|
|
|
String _getRowStatus(Map<String, dynamic> row) {
|
|
final raw = localTokenType == 'pre'
|
|
? (row['emp_status'] ?? row['status'])
|
|
: row['status'];
|
|
return raw?.toString().toLowerCase().trim() ?? '';
|
|
}
|
|
|
|
String? _getRowStatusRaw(Map<String, dynamic> row) {
|
|
final raw = localTokenType == 'pre'
|
|
? (row['emp_status'] ?? row['status'])
|
|
: row['status'];
|
|
final value = raw?.toString().trim();
|
|
return value == null || value.isEmpty ? null : value;
|
|
}
|
|
|
|
String _getLoggedInValue(Map<String, dynamic> row) {
|
|
final loggedInRaw = row['logged_in'] ?? row['emp_is_active'];
|
|
final value = loggedInRaw?.toString().toLowerCase().trim() ?? '';
|
|
|
|
// API can return: null / Yes / No / 1 / 0 / true / false.
|
|
if (value.isEmpty || value == 'null' || value == 'no' || value == '0' || value == 'false') {
|
|
return 'no';
|
|
}
|
|
return (value == 'yes' || value == '1' || value == 'true') ? 'yes' : 'no';
|
|
}
|
|
|
|
bool _isLoggedInRow(Map<String, dynamic> row) =>
|
|
_getLoggedInValue(row) == 'yes';
|
|
|
|
bool _isSelfRow(Map<String, dynamic> row) =>
|
|
row['relationship']?.toString().toLowerCase().trim() == 'self';
|
|
|
|
String _getLoggedInDisplayLabel(Map<String, dynamic> row) =>
|
|
_getLoggedInValue(row) == 'yes' ? 'Yes' : 'No';
|
|
|
|
Color _getLoggedInChipColor(Map<String, dynamic> row) =>
|
|
_getLoggedInValue(row) == 'yes'
|
|
? const Color(0xFFBDF9D9)
|
|
: const Color(0xFFE8EAF6);
|
|
|
|
bool _matchesPreStatusFilter(Map<String, dynamic> row, String filter) {
|
|
final status = _getRowStatus(row);
|
|
final relationship = row['relationship']?.toString().toLowerCase().trim() ?? '';
|
|
|
|
switch (filter) {
|
|
case 'emp_count':
|
|
return relationship == 'self';
|
|
case 'enrolled':
|
|
return _isEnrolledStatus(status);
|
|
case 'not_enrolled':
|
|
return !_isEnrolledStatus(status);
|
|
case 'logged_in':
|
|
return _isSelfRow(row) && _isLoggedInRow(row);
|
|
case 'not_logged_in':
|
|
return _isSelfRow(row) && !_isLoggedInRow(row);
|
|
case 'draft':
|
|
return status == 'draft';
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool _matchesSearch(Map<String, dynamic> row, String lowerQuery) {
|
|
final status = _getRowStatus(row);
|
|
|
|
bool statusMatch;
|
|
if (lowerQuery == 'active' || lowerQuery == 'inactive') {
|
|
statusMatch = status == lowerQuery;
|
|
} else {
|
|
statusMatch = status.contains(lowerQuery);
|
|
}
|
|
|
|
return row['name']?.toString().toLowerCase().contains(lowerQuery) == true ||
|
|
row['emp_code']?.toString().toLowerCase().contains(lowerQuery) ==
|
|
true ||
|
|
row['uhid']?.toString().toLowerCase().contains(lowerQuery) == true ||
|
|
row['relationship']?.toString().toLowerCase().contains(lowerQuery) ==
|
|
true ||
|
|
row['formatted_dob']
|
|
?.toString()
|
|
.replaceAll("/", "-")
|
|
.toLowerCase()
|
|
.contains(lowerQuery) ==
|
|
true ||
|
|
row['gender']?.toString().toLowerCase().contains(lowerQuery) == true ||
|
|
row['mobile']?.toString().toLowerCase().contains(lowerQuery) == true ||
|
|
row['email_corporate']?.toString().toLowerCase().contains(lowerQuery) ==
|
|
true ||
|
|
statusMatch;
|
|
}
|
|
|
|
void _refreshFilteredData() {
|
|
Iterable<Map<String, dynamic>> data = originalData;
|
|
|
|
final lowerQuery = searchController.text.toLowerCase().trim();
|
|
if (lowerQuery.isNotEmpty) {
|
|
data = data.where((row) => _matchesSearch(row, lowerQuery));
|
|
}
|
|
|
|
if (localTokenType == 'pre' && _preStatusFilter != null) {
|
|
data = data.where((row) => _matchesPreStatusFilter(row, _preStatusFilter!));
|
|
}
|
|
|
|
filteredData = data.toList();
|
|
hasAnyEcardLink = filteredData.any(
|
|
(item) => item['ecard_download_link'] != null,
|
|
);
|
|
}
|
|
|
|
void search(String query) {
|
|
setState(() {
|
|
_currentPage = 1;
|
|
_refreshFilteredData();
|
|
});
|
|
}
|
|
|
|
void _onPreStatusFilterTap(String filter) {
|
|
setState(() {
|
|
_preStatusFilter = filter;
|
|
_currentPage = 1;
|
|
_refreshFilteredData();
|
|
});
|
|
}
|
|
|
|
void _resetPreStatusFilter() {
|
|
setState(() {
|
|
_preStatusFilter = null;
|
|
_currentPage = 1;
|
|
_refreshFilteredData();
|
|
});
|
|
}
|
|
|
|
void exportToCsv(List<Map<String, dynamic>> data) {
|
|
List<List<String>> rows = [];
|
|
|
|
// Header
|
|
rows.add([
|
|
'Emp Code',
|
|
'Name',
|
|
'UHID',
|
|
'Relationship',
|
|
'Date Of Birth',
|
|
'Gender',
|
|
'Mobile',
|
|
'Email',
|
|
'Status'
|
|
]);
|
|
|
|
// Data rows
|
|
for (var item in data) {
|
|
rows.add([
|
|
item['emp_code'] ?? '',
|
|
item['name'] ?? '',
|
|
item['uhid'] ?? '',
|
|
item['relationship'] ?? '',
|
|
item['formatted_dob'] ?? '',
|
|
item['gender'] ?? '',
|
|
item['mobile'] ?? '',
|
|
item['email_corporate'] ?? '',
|
|
_getRowStatusRaw(item) ?? '',
|
|
]);
|
|
}
|
|
|
|
|
|
// Convert to CSV string
|
|
String csvData = const ListToCsvConverter().convert(rows);
|
|
|
|
// For Web: Create download
|
|
final bytes = utf8.encode(csvData);
|
|
final blob = html.Blob([bytes]);
|
|
final url = html.Url.createObjectUrlFromBlob(blob);
|
|
final String csvFileName = "policies(${localCardPolicyNo}).csv";
|
|
final anchor = html.AnchorElement(href: url)
|
|
..setAttribute("download", csvFileName)
|
|
..click();
|
|
html.Url.revokeObjectUrl(url);
|
|
handleExportAction();
|
|
}
|
|
|
|
Future<void> handleExportAction() async {
|
|
logDebug('handleExportAction');
|
|
|
|
final postId = await tokenService.readValue('empHrId');
|
|
final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
|
|
|
|
var activity = "export_empdata";
|
|
|
|
var activityPre = "export_preempdata";
|
|
dynamic response;
|
|
|
|
logDebug('postId - $postId');
|
|
logDebug('preId - $preId');
|
|
logDebug('activity - $activity');
|
|
|
|
try {
|
|
logDebug('10');
|
|
|
|
if (localTokenType == 'pre') {
|
|
response = await apiService.getPreLogHrActivity(
|
|
postId!, preId!, localToken!, activityPre);
|
|
} else if (localTokenType == 'post') {
|
|
response = await apiService.getPostLogHrActivity(
|
|
postId!, preId!, localToken!, activity);
|
|
}
|
|
|
|
if (response['status'] == 'success') {
|
|
logDebug('Request success');
|
|
} else {
|
|
// ToastHelper.showWarningToast(
|
|
// context, 'Request failed with status: ${response.statusCode}');
|
|
logDebug('Request failed with status: ${response['code']}');
|
|
}
|
|
} catch (e) {
|
|
logDebug('Exception occurred: $e');
|
|
}
|
|
}
|
|
|
|
dynamic _parseNumericId(dynamic value) {
|
|
if (value == null) return value;
|
|
return int.tryParse(value.toString()) ?? value;
|
|
}
|
|
|
|
List<String> _getInceptionExportStatus() {
|
|
switch (_preStatusFilter) {
|
|
case 'draft':
|
|
return ['draft'];
|
|
case 'enrolled':
|
|
return ['enrolled'];
|
|
default:
|
|
return ['enrolled'];
|
|
}
|
|
}
|
|
|
|
({String empCode, String empName}) _getInceptionSearchParams() {
|
|
final query = searchController.text.trim();
|
|
if (query.isEmpty) {
|
|
return (empCode: '', empName: '');
|
|
}
|
|
|
|
final lowerQuery = query.toLowerCase();
|
|
final codeMatch = originalData.any(
|
|
(row) =>
|
|
row['emp_code']?.toString().toLowerCase().contains(lowerQuery) ??
|
|
false,
|
|
);
|
|
final nameMatch = originalData.any(
|
|
(row) =>
|
|
row['name']?.toString().toLowerCase().contains(lowerQuery) ?? false,
|
|
);
|
|
|
|
if (codeMatch && !nameMatch) {
|
|
return (empCode: query, empName: '');
|
|
}
|
|
if (nameMatch && !codeMatch) {
|
|
return (empCode: '', empName: query);
|
|
}
|
|
return (empCode: query, empName: query);
|
|
}
|
|
|
|
Future<void> downloadInceptionExport() async {
|
|
try {
|
|
final clientId = await tokenService.readValue('enrollmentClient_id') ??
|
|
localClientId ??
|
|
widget.ClientId;
|
|
final branchId =
|
|
await tokenService.readValue('enrollmentEmpClientBranchId') ??
|
|
localClientBranchId ??
|
|
widget.clientBranchId;
|
|
final policyId = localClientPolicyId ?? widget.ClientPoliyId;
|
|
final searchParams = _getInceptionSearchParams();
|
|
final status = _getInceptionExportStatus();
|
|
|
|
logDebug(
|
|
'downloadInceptionExport client=$clientId branch=$branchId '
|
|
'policies=$policyId status=$status '
|
|
'empCode=${searchParams.empCode} empName=${searchParams.empName}',
|
|
);
|
|
|
|
final response = await apiService.downloadInception(
|
|
client: _parseNumericId(clientId),
|
|
branch: _parseNumericId(branchId),
|
|
policies: _parseNumericId(policyId),
|
|
status: status,
|
|
empCode: searchParams.empCode,
|
|
empName: searchParams.empName,
|
|
token: localToken!,
|
|
);
|
|
if (!mounted) return;
|
|
if (response['status'] == true) {
|
|
final downloadUrl = response['data']?['downloadUrl'];
|
|
if (downloadUrl != null && downloadUrl.toString().isNotEmpty) {
|
|
await _launchURL(downloadUrl.toString());
|
|
ToastHelper.showSuccessToast(
|
|
context,
|
|
response['message']?.toString() ??
|
|
'Inception export generated successfully.',
|
|
);
|
|
} else {
|
|
ToastHelper.showErrorToast(context, 'Download URL not available');
|
|
}
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ??
|
|
'Failed to generate inception export.',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logDebug('downloadInceptionExport error: $e');
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Failed to generate inception export. Please try again.',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _buildExportButton() {
|
|
final buttonStyle = ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFE26728),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
);
|
|
final textStyle = GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
letterSpacing: 1,
|
|
);
|
|
|
|
if (localTokenType == 'pre') {
|
|
return SizedBox(
|
|
width: 116,
|
|
height: 37,
|
|
child: PopupMenuButton<String>(
|
|
offset: const Offset(0, 37),
|
|
onSelected: (value) {
|
|
if (value == 'csv') {
|
|
exportToCsv(filteredData);
|
|
} else if (value == 'inception') {
|
|
downloadInceptionExport();
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
PopupMenuItem(
|
|
value: 'csv',
|
|
child: Text(
|
|
'Export',
|
|
style: GoogleFonts.poppins(fontSize: 13),
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'inception',
|
|
child: Text(
|
|
'Inception Export',
|
|
style: GoogleFonts.poppins(fontSize: 13),
|
|
),
|
|
),
|
|
],
|
|
child: IgnorePointer(
|
|
child: ElevatedButton(
|
|
onPressed: () {},
|
|
style: buttonStyle,
|
|
child: Text('Export', style: textStyle),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return SizedBox(
|
|
width: 116,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: () => exportToCsv(filteredData),
|
|
style: buttonStyle,
|
|
child: Text('Export', style: textStyle),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _capitalize(String? value) {
|
|
if (value == null || value.isEmpty) return '';
|
|
return value[0].toUpperCase() + value.substring(1).toLowerCase();
|
|
}
|
|
|
|
String _getStatusDisplayLabel(String? status) {
|
|
final normalized = status?.toLowerCase().trim() ?? '';
|
|
if (localTokenType == 'pre' && _isEnrolledStatus(normalized)) {
|
|
return 'Submitted';
|
|
}
|
|
return _capitalize(status);
|
|
}
|
|
|
|
Future<void> _openPolicyTermsDialog() async {
|
|
setState(() => _isLoadingPolicyTerms = true);
|
|
|
|
try {
|
|
final clientId = localClientId ?? widget.ClientId;
|
|
final branchId = localClientBranchId ?? widget.clientBranchId;
|
|
final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId;
|
|
final token = localToken ?? widget.Token;
|
|
final hrId = await tokenService.readValue('empHrId');
|
|
|
|
if (hrId == null || hrId.toString().isEmpty) {
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(context, 'HR ID not found');
|
|
}
|
|
return;
|
|
}
|
|
|
|
final response = await apiService.getActiveCashDepositDetailsToApi(
|
|
clientId,
|
|
branchId,
|
|
hrId.toString(),
|
|
token,
|
|
1,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
if (response['status'] != 'success') {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Failed to load policy terms',
|
|
);
|
|
return;
|
|
}
|
|
|
|
final policies = List<Map<String, dynamic>>.from(response['data'] ?? []);
|
|
Map<String, dynamic>? matchedPolicy;
|
|
for (final policy in policies) {
|
|
if (policy['client_policy_id'].toString() == clientPolicyId) {
|
|
matchedPolicy = policy;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matchedPolicy == null) {
|
|
ToastHelper.showErrorToast(context, 'Policy not found');
|
|
return;
|
|
}
|
|
|
|
final rawTerms =
|
|
matchedPolicy['policy_terms'] ?? matchedPolicy['Policy_Terms'];
|
|
final terms = <String, String>{};
|
|
|
|
if (rawTerms is Map) {
|
|
rawTerms.forEach((key, value) {
|
|
if (value != null && value.toString().trim().isNotEmpty) {
|
|
terms[key.toString()] = value.toString();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (terms.isEmpty) {
|
|
ToastHelper.showWarningToast(context, 'No policy terms available');
|
|
return;
|
|
}
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
builder: (dialogContext) => _PolicyTermsDialog(terms: terms),
|
|
);
|
|
} catch (e) {
|
|
logDebug('Policy terms load failed: $e');
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(context, 'Failed to load policy terms');
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isLoadingPolicyTerms = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _openReminderConfigDialog() async {
|
|
final clientId = localClientId ?? widget.ClientId;
|
|
final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId;
|
|
final clientBranchId = localClientBranchId ?? widget.clientBranchId;
|
|
final token = localToken ?? widget.Token;
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (dialogContext) {
|
|
return _ReminderMailConfigDialog(
|
|
clientId: clientId,
|
|
clientPolicyId: clientPolicyId,
|
|
clientBranchId: clientBranchId,
|
|
token: token,
|
|
apiService: apiService,
|
|
defaultSubject: _defaultReminderSubject(),
|
|
defaultHtmlBody: _defaultReminderHtmlBody(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
String _defaultReminderSubject() {
|
|
final policyName = localCardPolicyName ?? widget.cardPolicy_name;
|
|
return 'Reminder: Complete Your Enrollment - $policyName';
|
|
}
|
|
|
|
String _defaultReminderHtmlBody() {
|
|
return kDefaultEnrollmentReminderHtmlBody;
|
|
}
|
|
|
|
Future<void> _openReminderTemplateDialog() async {
|
|
final clientId = localClientId ?? widget.ClientId;
|
|
final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId;
|
|
final clientBranchId = localClientBranchId ?? widget.clientBranchId;
|
|
final token = localToken ?? widget.Token;
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (dialogContext) {
|
|
return ReminderEmailTemplateDialog(
|
|
clientId: clientId,
|
|
clientPolicyId: clientPolicyId,
|
|
clientBranchId: clientBranchId,
|
|
token: token,
|
|
apiService: apiService,
|
|
defaultSubject: _defaultReminderSubject(),
|
|
defaultHtmlBody: _defaultReminderHtmlBody(),
|
|
mode: ReminderEmailTemplateMode.send,
|
|
onSend: (subject, htmlBody) => _sendEnrollmentReminder(
|
|
emailSubject: subject,
|
|
emailBody: htmlBody,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _sendEnrollmentReminder({
|
|
required String emailSubject,
|
|
required String emailBody,
|
|
}) async {
|
|
setState(() => _isSendingReminder = true);
|
|
|
|
try {
|
|
final hrId = await tokenService.readValue('enrollmentHrId');
|
|
final response = await apiService.sendReminderMailApi(
|
|
localClientId ?? widget.ClientId,
|
|
localClientPolicyId ?? widget.ClientPoliyId,
|
|
localClientBranchId ?? widget.clientBranchId,
|
|
hrId ?? '',
|
|
localToken ?? widget.Token,
|
|
emailSubject: emailSubject,
|
|
emailBody: emailBody,
|
|
);
|
|
|
|
final ok = response['status'] == 'success' || response['status'] == true;
|
|
if (ok) {
|
|
final message =
|
|
response['message']?.toString() ?? 'Reminder sent successfully';
|
|
ToastHelper.showSuccessToast(context, message);
|
|
await _logReminderActivity();
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Failed to send reminder',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logDebug('Reminder exception: $e');
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Failed to send reminder. Please try again.',
|
|
);
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isSendingReminder = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _logReminderActivity() async {
|
|
final postId = await tokenService.readValue('empHrId');
|
|
final preId = await tokenService.readValue('enrollmentEmpPrimaryId');
|
|
const activityPre = 'send_enrollment_reminder';
|
|
|
|
try {
|
|
await apiService.getPreLogHrActivity(
|
|
postId!,
|
|
preId!,
|
|
localToken!,
|
|
activityPre,
|
|
);
|
|
} catch (e) {
|
|
logDebug('Reminder activity log failed: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> getEcardBulkDownload() async {
|
|
try {
|
|
final emp_policy_ids = selectedEmployeeIds.toList();
|
|
logDebug('10 $emp_policy_ids');
|
|
empHrId = await tokenService.readValue('empHrId');
|
|
final response = await apiService.getEcardBulkDownloadApi(
|
|
'', empHrId, emp_policy_ids, localToken!);
|
|
if (response['status'] == true) {
|
|
logDebug('Request success');
|
|
_showBulkDownloadSuccessPopup(response['message']);
|
|
} else {
|
|
ToastHelper.showErrorToast(context, response['message']);
|
|
logDebug('Request failed with status: ${response['code']}');
|
|
}
|
|
} catch (e) {
|
|
logDebug('Exception occurred: $e');
|
|
}
|
|
}
|
|
|
|
void _showBulkDownloadSuccessPopup(String message) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(
|
|
Icons.check_circle,
|
|
color: Color(0xFF009195),
|
|
size: 60,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF009195),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
child: Text(
|
|
'OK',
|
|
style: GoogleFonts.poppins(color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BaseLayout(
|
|
child: _buildContent(context),
|
|
);
|
|
}
|
|
|
|
Widget _buildContent(BuildContext context) {
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: Stack(
|
|
children: [
|
|
// ===================== MAIN CONTENT =====================
|
|
Column(
|
|
children: [
|
|
// ---------------- HEADER ----------------
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
/// 🔙 Back + Title (LEFT)
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
tooltip: 'Previous Page',
|
|
onPressed: () async => {
|
|
await clearPolicyStorage(),
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => policies(),
|
|
),
|
|
)
|
|
},
|
|
// splashRadius: 20,
|
|
icon: const Icon(
|
|
Icons.arrow_back_ios,
|
|
size: 18,
|
|
color: Colors.black,
|
|
),
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Container(
|
|
// color: Colors.redAccent.shade100,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"${localCardType} - ${localCardPolicyNo} " ??
|
|
'',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Text(
|
|
localTokenType == 'pre'
|
|
? "${localCardPolicyName} (${localCardPolicyExpDate})"
|
|
: "${localCardInsurerName} - ${localCardPolicyName} (${localCardPolicyExpDate})",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.grey,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
/// Push right content to end
|
|
const Spacer(),
|
|
|
|
if (localIsEcardBulkDownload == 1) ...[
|
|
const SizedBox(width: 12),
|
|
SizedBox(
|
|
width: 40,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
getEcardBulkDownload();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFE26728),
|
|
elevation: 0,
|
|
padding: EdgeInsets.zero, // ✅ IMPORTANT
|
|
alignment: Alignment.center, // ✅ FORCE CENTER
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: Icon(
|
|
Icons.credit_card,
|
|
size: 18,
|
|
color: Colors.white,
|
|
)),
|
|
),
|
|
],
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
if (localTokenType == 'pre') ...[
|
|
SizedBox(
|
|
width: 150,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: _openReminderConfigDialog,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF009195),
|
|
elevation: 0,
|
|
padding: EdgeInsets.zero,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Reminder Config',
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
SizedBox(
|
|
width: 142,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: _isSendingReminder
|
|
? null
|
|
: _openReminderTemplateDialog,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFE26728),
|
|
elevation: 0,
|
|
padding: EdgeInsets.zero,
|
|
disabledBackgroundColor:
|
|
const Color(0xFFE26728).withValues(alpha: 0.6),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: _isSendingReminder
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: Text(
|
|
'Reminder',
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
],
|
|
|
|
if (localTokenType == 'post') ...[
|
|
SizedBox(
|
|
width: 130,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoadingPolicyTerms
|
|
? null
|
|
: _openPolicyTermsDialog,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF009195),
|
|
elevation: 0,
|
|
padding: EdgeInsets.zero,
|
|
disabledBackgroundColor:
|
|
const Color(0xFF009195).withValues(alpha: 0.6),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: _isLoadingPolicyTerms
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: Text(
|
|
'Policy Terms',
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
],
|
|
|
|
SizedBox(
|
|
width: 116,
|
|
height: 37,
|
|
child: ElevatedButton(
|
|
onPressed: () async {
|
|
await tokenService.writeValue('upload_ClientId',
|
|
widget.ClientId.toString());
|
|
await tokenService.writeValue(
|
|
'upload_policyTypeId',
|
|
widget.policyTypeId.toString());
|
|
await tokenService.writeValue(
|
|
'upload_ClientPoliyId',
|
|
widget.ClientPoliyId.toString());
|
|
await tokenService.writeValue(
|
|
'upload_clientBranchId',
|
|
widget.clientBranchId.toString());
|
|
await tokenService.writeValue(
|
|
'upload_Token', widget.Token.toString());
|
|
await tokenService.writeValue('upload_TokenType',
|
|
widget.TokenType.toString());
|
|
await tokenService.writeValue('upload_cardType',
|
|
widget.cardType.toString());
|
|
await tokenService.writeValue(
|
|
'upload_cardPolicyNo',
|
|
widget.cardPolicyNo.toString());
|
|
await tokenService.writeValue(
|
|
'upload_cardInsurer_name',
|
|
widget.cardInsurer_name.toString());
|
|
await tokenService.writeValue(
|
|
'upload_cardPolicy_name',
|
|
widget.cardPolicy_name.toString());
|
|
await tokenService.writeValue(
|
|
'upload_cardPolicy_ExpDate',
|
|
widget.cardPolicy_ExpDate.toString());
|
|
await tokenService.writeValue(
|
|
'upload_total_premium',
|
|
widget.total_premium.toString());
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
settings: localTokenType != "post"
|
|
? RouteSettings(name: 'preFileUpload')
|
|
: RouteSettings(name: 'postFileUpload'),
|
|
builder: (context) => localTokenType !=
|
|
"post"
|
|
? preFileUpload(
|
|
ClientId:
|
|
widget.ClientId, // <-- from map
|
|
policyTypeId: widget.policyTypeId,
|
|
ClientPoliyId: widget.ClientPoliyId,
|
|
clientBranchId:
|
|
widget.clientBranchId,
|
|
Token: widget.Token,
|
|
TokenType: widget.TokenType,
|
|
cardType: widget.cardType,
|
|
cardPolicyNo: widget.cardPolicyNo,
|
|
cardInsurer_name:
|
|
widget.cardInsurer_name,
|
|
cardPolicy_name:
|
|
widget.cardPolicy_name,
|
|
cardPolicy_ExpDate:
|
|
widget.cardPolicy_ExpDate,
|
|
total_premium: widget.total_premium,
|
|
|
|
// Token: widget.Token,
|
|
// ClientId: widget.ClientId,
|
|
// ClientPolicyId : widget.ClientPoliyId,
|
|
// PolicyName: widget.cardPolicy_name,
|
|
// PolicyNo: widget.cardPolicyNo,
|
|
// ClientBranchId: widget.HrId,
|
|
// PolicyType: widget.cardType,
|
|
)
|
|
: postFileUpload(
|
|
ClientId:
|
|
widget.ClientId, // <-- from map
|
|
policyTypeId: widget.policyTypeId,
|
|
ClientPoliyId: widget.ClientPoliyId,
|
|
clientBranchId:
|
|
widget.clientBranchId,
|
|
Token: widget.Token,
|
|
TokenType: widget.TokenType,
|
|
cardType: widget.cardType,
|
|
cardPolicyNo: widget.cardPolicyNo,
|
|
cardInsurer_name:
|
|
widget.cardInsurer_name,
|
|
cardPolicy_name:
|
|
widget.cardPolicy_name,
|
|
cardPolicy_ExpDate:
|
|
widget.cardPolicy_ExpDate,
|
|
total_premium: widget.total_premium,
|
|
allocgType: 'EB',
|
|
|
|
// Token: localToken,
|
|
// ClientId: widget.ClientId,
|
|
// ClientPolicyId : widget.ClientPoliyId,
|
|
// PolicyName: widget.cardPolicy_name,
|
|
// PolicyNo: widget.cardPolicyNo,
|
|
// ClientBranchId: widget.HrId,
|
|
// PolicyType: widget.cardType,
|
|
)),
|
|
);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFE26728),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Import',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
letterSpacing: 1,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
|
|
/// ⬇️ Export Button
|
|
_buildExportButton(),
|
|
],
|
|
),
|
|
]),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ---------------- LOADER CONDITION ----------------
|
|
// This shows a linear progress bar if the site is fetching data
|
|
if (isLoading)
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: LinearProgressIndicator(color: Color(0xFFE26728)),
|
|
),
|
|
|
|
// ---------------- PREMIUM / STATUS ----------------
|
|
if (localTokenType == "pre")
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 15, right: 16),
|
|
child: _buildStatusSummary(),
|
|
),
|
|
|
|
if (localTokenType == "post")
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF9EBBD),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text(
|
|
'Premium - ₹${localTotalPremium}',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: const Color(0xFF009195),
|
|
),
|
|
),
|
|
),
|
|
const Spacer(),
|
|
_buildCompactSearchField(),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ---------------- TABLE (SCROLLABLE) ----------------
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: isLoading
|
|
? Container(
|
|
color: Colors
|
|
.transparent, // Semi-transparent background
|
|
child: Center(
|
|
child: // Your GIF loader widget
|
|
Image.asset(
|
|
height: 60,
|
|
width: 60,
|
|
'assets/nhance-loader.gif'), // Adjust path to your GIF loader
|
|
),
|
|
)
|
|
: Column(
|
|
children: [
|
|
_buildCDDataTable(
|
|
context), // ← DO NOT wrap again in Expanded
|
|
],
|
|
)),
|
|
),
|
|
|
|
const SizedBox(height: 48),
|
|
],
|
|
),
|
|
|
|
// ===================== FOOTER TEXT =====================
|
|
Positioned(
|
|
bottom: 12,
|
|
right: 16,
|
|
child: Text(
|
|
'(* Premium may vary subject to claims)',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 11,
|
|
color: Colors.red,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget _buildContent(BuildContext context) {
|
|
// return Container(
|
|
// // padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50),
|
|
// child: Column(
|
|
// children: [
|
|
// Row(
|
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
|
// children: [
|
|
// /// 🔙 Back + Title (LEFT)
|
|
// Row(
|
|
// children: [
|
|
// IconButton(
|
|
// onPressed: () => {Navigator.pop(context)},
|
|
// icon: const Icon(
|
|
// Icons.arrow_back_ios,
|
|
// size: 18,
|
|
// color: Colors.black,
|
|
// ),
|
|
// padding: EdgeInsets.zero,
|
|
// constraints: const BoxConstraints(),
|
|
// ),
|
|
// const SizedBox(width: 6),
|
|
// Container(
|
|
// // color: Colors.redAccent.shade100,
|
|
// child: Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// mainAxisAlignment: MainAxisAlignment.start,
|
|
// children: [
|
|
// Text(
|
|
// "${widget.cardType} - ${widget.cardPolicyNo} " ??
|
|
// '',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontSize: 14,
|
|
// fontWeight: FontWeight.w500,
|
|
// ),
|
|
// ),
|
|
// Text(
|
|
// localTokenType == 'pre'
|
|
// ? "${widget.cardPolicy_name} (${widget.cardPolicy_ExpDate})"
|
|
// : "${widget.cardInsurer_name} - ${widget.cardPolicy_name} (${widget.cardPolicy_ExpDate})",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.grey,
|
|
// fontSize: 12,
|
|
// fontWeight: FontWeight.w400,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
//
|
|
// /// Push right content to end
|
|
// const Spacer(),
|
|
//
|
|
// /// 🔍 Search Box
|
|
// Container(
|
|
// width: 380,
|
|
// height: 37,
|
|
// decoration: BoxDecoration(
|
|
// color: const Color(0xFFF0F0F0),
|
|
// borderRadius: BorderRadius.circular(8),
|
|
// ),
|
|
// child: TextField(
|
|
// controller: searchController,
|
|
// onChanged: search,
|
|
// style: GoogleFonts.poppins(fontSize: 14),
|
|
// decoration: const InputDecoration(
|
|
// hintText: 'Search',
|
|
// prefixIcon: Icon(Icons.search, size: 18),
|
|
// border: InputBorder.none,
|
|
// contentPadding:
|
|
// EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
//
|
|
// if (localIsEcardBulkDownload == 1) ...[
|
|
// const SizedBox(width: 12),
|
|
// SizedBox(
|
|
// width: 40,
|
|
// height: 37,
|
|
// child: ElevatedButton(
|
|
// onPressed: () {
|
|
// getEcardBulkDownload();
|
|
// },
|
|
// style: ElevatedButton.styleFrom(
|
|
// backgroundColor: const Color(0xFFE26728),
|
|
// elevation: 0,
|
|
// padding: EdgeInsets.zero, // ✅ IMPORTANT
|
|
// alignment: Alignment.center, // ✅ FORCE CENTER
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(10),
|
|
// ),
|
|
// ),
|
|
// child: Icon(
|
|
// Icons.credit_card,
|
|
// size: 18,
|
|
// color: Colors.white,
|
|
// )),
|
|
// ),
|
|
// ],
|
|
//
|
|
// const SizedBox(width: 12),
|
|
//
|
|
// SizedBox(
|
|
// width: 116,
|
|
// height: 37,
|
|
// child: ElevatedButton(
|
|
// onPressed: () {
|
|
// Navigator.push(
|
|
// context,
|
|
// MaterialPageRoute(
|
|
// builder: (context) => localTokenType != "post"
|
|
// ? preFileUpload(
|
|
// ClientId:
|
|
// localClientId, // <-- from map
|
|
// policyTypeId: localPolicyTypeId,
|
|
// ClientPoliyId: localClientPolicyId,
|
|
// clientBranchId: widget.clientBranchId,
|
|
// Token: widget.Token,
|
|
// TokenType: localTokenType,
|
|
// cardType: widget.cardType,
|
|
// cardPolicyNo: widget.cardPolicyNo,
|
|
// cardInsurer_name:
|
|
// widget.cardInsurer_name,
|
|
// cardPolicy_name: widget.cardPolicy_name,
|
|
// cardPolicy_ExpDate:
|
|
// widget.cardPolicy_ExpDate,
|
|
// total_premium: widget.total_premium,
|
|
//
|
|
// // Token: widget.Token,
|
|
// // ClientId: localClientId,
|
|
// // ClientPolicyId : localClientPolicyId,
|
|
// // PolicyName: widget.cardPolicy_name,
|
|
// // PolicyNo: widget.cardPolicyNo,
|
|
// // ClientBranchId: widget.HrId,
|
|
// // PolicyType: widget.cardType,
|
|
// )
|
|
// : postFileUpload(
|
|
// ClientId:
|
|
// localClientId, // <-- from map
|
|
// policyTypeId: localPolicyTypeId,
|
|
// ClientPoliyId: localClientPolicyId,
|
|
// clientBranchId: widget.clientBranchId,
|
|
// Token: widget.Token,
|
|
// TokenType: localTokenType,
|
|
// cardType: widget.cardType,
|
|
// cardPolicyNo: widget.cardPolicyNo,
|
|
// cardInsurer_name:
|
|
// widget.cardInsurer_name,
|
|
// cardPolicy_name: widget.cardPolicy_name,
|
|
// cardPolicy_ExpDate:
|
|
// widget.cardPolicy_ExpDate,
|
|
// total_premium: widget.total_premium,
|
|
//
|
|
// // Token: widget.Token,
|
|
// // ClientId: localClientId,
|
|
// // ClientPolicyId : localClientPolicyId,
|
|
// // PolicyName: widget.cardPolicy_name,
|
|
// // PolicyNo: widget.cardPolicyNo,
|
|
// // ClientBranchId: widget.HrId,
|
|
// // PolicyType: widget.cardType,
|
|
// )),
|
|
// );
|
|
// },
|
|
// style: ElevatedButton.styleFrom(
|
|
// backgroundColor: const Color(0xFFE26728),
|
|
// elevation: 0,
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(10),
|
|
// ),
|
|
// ),
|
|
// child: Text(
|
|
// 'Import',
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: 16,
|
|
// fontWeight: FontWeight.w700,
|
|
// color: Colors.white,
|
|
// letterSpacing: 1,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// const SizedBox(width: 12),
|
|
//
|
|
// /// ⬇️ Export Button
|
|
// SizedBox(
|
|
// width: 116,
|
|
// height: 37,
|
|
// child: ElevatedButton(
|
|
// onPressed: () {
|
|
// exportToCsv(filteredData);
|
|
// },
|
|
// style: ElevatedButton.styleFrom(
|
|
// backgroundColor: const Color(0xFFE26728),
|
|
// elevation: 0,
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(10),
|
|
// ),
|
|
// ),
|
|
// child: Text(
|
|
// 'Export',
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: 16,
|
|
// fontWeight: FontWeight.w700,
|
|
// color: Colors.white,
|
|
// letterSpacing: 1,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// SizedBox(height: 20),
|
|
// if (localTokenType == "pre") ...[_buildStatusSummary()],
|
|
// if (localTokenType == "post") ...[
|
|
// Row(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Container(
|
|
// padding: const EdgeInsets.symmetric(
|
|
// horizontal: 12, vertical: 8),
|
|
// decoration: BoxDecoration(
|
|
// color: Color(0xFFF9EBBD),
|
|
// borderRadius: BorderRadius.circular(6),
|
|
// ),
|
|
// child: Text(
|
|
// 'Premium - ₹${widget.total_premium ?? ''}*',
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: 12,
|
|
// fontWeight: FontWeight.w600,
|
|
// color: Color(0xFF009195),
|
|
// ),
|
|
// ),
|
|
// )
|
|
// ],
|
|
// ),
|
|
// ],
|
|
// SizedBox(height: 20),
|
|
// Container(
|
|
// // decoration: BoxDecoration(
|
|
// // color: Colors.white,
|
|
// // borderRadius:
|
|
// // BorderRadius.circular(10), // 👈 set your desired radius
|
|
// // ),
|
|
// // height: 400,
|
|
// // margin: const EdgeInsets.only(left: 40.0, right: 40.0),
|
|
// // padding: const EdgeInsets.all(16.0),
|
|
// child: Column(
|
|
// children: [
|
|
// Row(
|
|
// children: [
|
|
// Expanded(
|
|
// child: Container(
|
|
// child: SingleChildScrollView(
|
|
// scrollDirection: Axis.vertical,
|
|
// child: _buildCDDataTable(context),
|
|
// ),
|
|
// ),
|
|
// )
|
|
// ],
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// Positioned(
|
|
// bottom: 12,
|
|
// right: 16,
|
|
// child: Text(
|
|
// '(* Premium may vary subject to claims)',
|
|
// textAlign: TextAlign.right,
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: 11,
|
|
// color: Colors.red,
|
|
// fontStyle: FontStyle.italic,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
Map<String, int> getPreStatusCounts() {
|
|
int empCount = 0;
|
|
int enrolled = 0;
|
|
int notEnrolled = 0;
|
|
int loggedIn = 0;
|
|
int notLoggedIn = 0;
|
|
int draft = 0;
|
|
|
|
for (final item in originalData) {
|
|
final status = _getRowStatus(item);
|
|
final relationship =
|
|
item['relationship']?.toString().toLowerCase().trim() ?? '';
|
|
|
|
if (relationship == 'self') empCount++;
|
|
if (_isEnrolledStatus(status)) {
|
|
enrolled++;
|
|
} else {
|
|
notEnrolled++;
|
|
}
|
|
if (_isSelfRow(item)) {
|
|
if (_isLoggedInRow(item)) {
|
|
loggedIn++;
|
|
} else {
|
|
notLoggedIn++;
|
|
}
|
|
}
|
|
if (status == 'draft') draft++;
|
|
}
|
|
|
|
return {
|
|
'emp_count': empCount,
|
|
'enrolled': enrolled,
|
|
'not_enrolled': notEnrolled,
|
|
'logged_in': loggedIn,
|
|
'not_logged_in': notLoggedIn,
|
|
'draft': draft,
|
|
};
|
|
}
|
|
|
|
Color _getPreFilterChipColor(String filter) {
|
|
switch (filter) {
|
|
case 'emp_count':
|
|
return const Color(0xFFE2FBCB);
|
|
case 'enrolled':
|
|
return const Color(0xFFBDF9D9);
|
|
case 'not_enrolled':
|
|
return const Color(0xFFFFE8AC);
|
|
case 'logged_in':
|
|
return const Color(0xFFC5F2F4);
|
|
case 'not_logged_in':
|
|
return const Color(0xFFE8EAF6);
|
|
case 'draft':
|
|
return const Color(0xFFF9EBBD);
|
|
default:
|
|
return const Color(0xFFB0BEC5);
|
|
}
|
|
}
|
|
|
|
Widget _buildCompactSearchField({double width = 180}) {
|
|
return SizedBox(
|
|
width: width,
|
|
height: 32,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF0F0F0),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: TextField(
|
|
controller: searchController,
|
|
onChanged: search,
|
|
textAlign: TextAlign.left,
|
|
textAlignVertical: TextAlignVertical.center,
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
hintText: 'Search',
|
|
hintStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.black45),
|
|
prefixIcon: const Icon(Icons.search, size: 16),
|
|
prefixIconConstraints:
|
|
const BoxConstraints(minWidth: 28, minHeight: 32),
|
|
isDense: true,
|
|
border: InputBorder.none,
|
|
contentPadding:
|
|
const EdgeInsets.only(left: 0, right: 10, top: 0, bottom: 0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPreStatusFilterChip({
|
|
required String filterKey,
|
|
required String label,
|
|
required int count,
|
|
}) {
|
|
final selected = _preStatusFilter == filterKey;
|
|
final color = _getPreFilterChipColor(filterKey);
|
|
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () => _onPreStatusFilterTap(filterKey),
|
|
borderRadius: BorderRadius.circular(6),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(
|
|
color: selected ? const Color(0xFF009195) : Colors.transparent,
|
|
width: 2,
|
|
),
|
|
),
|
|
child: Text(
|
|
'$label - $count',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: selected ? FontWeight.w700 : FontWeight.w600,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusSummary() {
|
|
if (originalData.isEmpty) {
|
|
return const SizedBox();
|
|
}
|
|
|
|
final statusCounts = getPreStatusCounts();
|
|
const filters = <Map<String, String>>[
|
|
{'key': 'emp_count', 'label': 'Emp Count'},
|
|
{'key': 'enrolled', 'label': 'Submitted'},
|
|
{'key': 'logged_in', 'label': 'Logged-In'},
|
|
{'key': 'not_logged_in', 'label': 'Not Logged-In'},
|
|
{'key': 'draft', 'label': 'Draft'},
|
|
];
|
|
|
|
return SizedBox(
|
|
height: 38,
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Expanded(
|
|
child: ListView.separated(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: filters.length + 1,
|
|
separatorBuilder: (_, __) => const SizedBox(width: 10),
|
|
itemBuilder: (context, index) {
|
|
if (index == filters.length) {
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap:
|
|
_preStatusFilter == null ? null : _resetPreStatusFilter,
|
|
borderRadius: BorderRadius.circular(6),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF0F0F0),
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(
|
|
color: _preStatusFilter != null
|
|
? const Color(0xFF009195)
|
|
: const Color(0xFFD0D0D0),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Reset',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: _preStatusFilter != null
|
|
? const Color(0xFF009195)
|
|
: Colors.black54,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final filter = filters[index];
|
|
return _buildPreStatusFilterChip(
|
|
filterKey: filter['key']!,
|
|
label: filter['label']!,
|
|
count: statusCounts[filter['key']] ?? 0,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
_buildCompactSearchField(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCDDataTable(BuildContext context) {
|
|
if (filteredData.isEmpty) {
|
|
return const Center(child: Text('No data available'));
|
|
}
|
|
|
|
return Expanded(
|
|
// ✅ VERY IMPORTANT
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
/// 🔒 Sticky Header
|
|
SliverPersistentHeader(
|
|
pinned: true,
|
|
delegate: _CDHeaderDelegate(
|
|
showUHID: localPolicyTypeId != '6' && localPolicyTypeId != '7',
|
|
showLoggedIn: localTokenType == "pre",
|
|
showAction:
|
|
localTokenType != "pre" && (hasAnyEcardLink || hasModule),
|
|
),
|
|
),
|
|
|
|
/// 📄 Rows
|
|
SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
final item = _paginatedData[index];
|
|
return _buildCDRow(item);
|
|
},
|
|
childCount: _paginatedData.length,
|
|
),
|
|
),
|
|
|
|
/// 📌 Pagination
|
|
SliverToBoxAdapter(
|
|
child: _buildPagination(context),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCDRow(Map<String, dynamic> item) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
|
margin: const EdgeInsets.only(top: 6),
|
|
decoration: const BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(color: Color(0xFFA9D9DE)),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
/// NAME
|
|
Expanded(
|
|
flex: 3,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(item['name'] ?? '-', style: _dataBold),
|
|
Text(item['emp_code'] ?? '-', style: _dataSub),
|
|
],
|
|
),
|
|
),
|
|
|
|
/// UHID
|
|
if (localPolicyTypeId != '6' && localPolicyTypeId != '7')
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(item['uhid'] ?? '-', style: _dataBold),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(item['relationship'] ?? '-', style: _dataBold),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(item['formatted_dob']?.replaceAll("/", "-") ?? '-',
|
|
style: _dataBold),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(item['gender'] ?? '-', style: _dataBold),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(item['mobile'] ?? '-', style: _dataBold),
|
|
),
|
|
|
|
Expanded(
|
|
flex: 5,
|
|
child: Text(item['email_corporate'] ?? '-', style: _dataBold),
|
|
),
|
|
|
|
if (localTokenType == "pre")
|
|
Expanded(
|
|
flex: 2,
|
|
child: _isSelfRow(item)
|
|
? Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _getLoggedInChipColor(item),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Text(
|
|
_getLoggedInDisplayLabel(item),
|
|
textAlign: TextAlign.center,
|
|
style: _dataBold,
|
|
),
|
|
)
|
|
: Text('-', style: _dataBold),
|
|
),
|
|
|
|
if (localTokenType == "pre") const SizedBox(width: 8),
|
|
|
|
/// STATUS
|
|
Expanded(
|
|
flex: 2,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: getStatusColor(_getRowStatusRaw(item) ?? ''),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Text(
|
|
_getStatusDisplayLabel(_getRowStatusRaw(item)),
|
|
textAlign: TextAlign.center,
|
|
style: _dataBold,
|
|
),
|
|
),
|
|
),
|
|
|
|
/// ACTION
|
|
|
|
if (localTokenType != "pre" && (hasAnyEcardLink || hasModule))
|
|
Expanded(
|
|
flex: 3,
|
|
child: Builder(
|
|
builder: (context) {
|
|
final isSelf = item['relationship'] == 'Self';
|
|
final hasEcard = item['ecard_download_link'] != null;
|
|
final showEcard = isSelf && hasEcard;
|
|
final showClaim = localTokenType == "post" && hasModule;
|
|
|
|
if (!showEcard && !showClaim) {
|
|
return SizedBox(); // No icon to show
|
|
}
|
|
|
|
return SizedBox(
|
|
height: 40,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
/// --- eCard Menu (Fixed Space) ---
|
|
SizedBox(
|
|
width: 40,
|
|
height: 40,
|
|
child: Visibility(
|
|
visible: showEcard,
|
|
maintainSize: true,
|
|
maintainAnimation: true,
|
|
maintainState: true,
|
|
child: PopupMenuButton<String>(
|
|
tooltip: 'Ecard',
|
|
offset: const Offset(0, 40),
|
|
onSelected: (value) {
|
|
switch (value) {
|
|
case 'download':
|
|
getEcardDownload(
|
|
item['emp_code'],
|
|
item['employee_id'],
|
|
item['client_policy_id'],
|
|
item['policy_no'],
|
|
);
|
|
break;
|
|
case 'email':
|
|
sendEcardViaEmail(
|
|
item['id'],
|
|
item['client_policy_id'],
|
|
);
|
|
break;
|
|
case 'copy':
|
|
copyEcardLink(
|
|
item['ecard_download_link']?.toString(),
|
|
);
|
|
break;
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
PopupMenuItem(
|
|
value: 'download',
|
|
child: Text(
|
|
'Download Ecard',
|
|
style: GoogleFonts.poppins(fontSize: 13),
|
|
),
|
|
),
|
|
if (isSelf)
|
|
PopupMenuItem(
|
|
value: 'email',
|
|
child: Text(
|
|
'Send E-card via email',
|
|
style: GoogleFonts.poppins(fontSize: 13),
|
|
),
|
|
),
|
|
PopupMenuItem(
|
|
value: 'copy',
|
|
child: Text(
|
|
'Copy link',
|
|
style: GoogleFonts.poppins(fontSize: 13),
|
|
),
|
|
),
|
|
],
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFE6F5F6),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.all(6),
|
|
child: Image.asset(
|
|
'assets/credit_card.png',
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
/// --- Claim Button (Fixed Space) ---
|
|
SizedBox(
|
|
width: 40,
|
|
height: 40,
|
|
child: Visibility(
|
|
visible: showClaim,
|
|
maintainSize: true,
|
|
maintainAnimation: true,
|
|
maintainState: true,
|
|
child: Tooltip(
|
|
message: 'View Claims',
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => ClaimsPolicies(
|
|
empCode: item['emp_code']!,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFE6F5F6),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.all(6),
|
|
child: Image.asset(
|
|
'assets/claim.png',
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget _buildCDDataTable(BuildContext context) {
|
|
// if (_paginatedData.isNotEmpty) {
|
|
// hasAnyEcardLink = _paginatedData.any(
|
|
// (item) => item['ecard_download_link'] != null,
|
|
// );
|
|
//
|
|
// if (localTokenType == "post" && hasModule) {
|
|
// logDebug("paginatTtt - $_paginatedData");
|
|
//
|
|
// logDebug("📥 Any e-card link present: $hasAnyEcardLink");
|
|
// }
|
|
// }
|
|
//
|
|
// logDebug('widgetpolicyTypeId');
|
|
// logDebug(localPolicyTypeId);
|
|
//
|
|
// if (filteredData.isEmpty) {
|
|
// return SizedBox(
|
|
// // height: 50,
|
|
// child: Center(
|
|
// child: Text(
|
|
// 'No data is available for the selected policy',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.grey,
|
|
// fontWeight: FontWeight.w400,
|
|
// ),
|
|
// )),
|
|
// );
|
|
// }
|
|
//
|
|
// final currentPageIds = _paginatedData
|
|
// .map((e) => e['id']?.toString())
|
|
// .whereType<String>()
|
|
// .toList();
|
|
//
|
|
// return Container(
|
|
// child: Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// // Header row
|
|
// Container(
|
|
// decoration: BoxDecoration(
|
|
// color: Color(0xFFD7E9EB),
|
|
// borderRadius: BorderRadius.circular(6),
|
|
// ),
|
|
// padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
// child: Row(
|
|
// children: [
|
|
// if (localTokenType == 'post' &&
|
|
// localIsEcardBulkDownload == 1)
|
|
// SizedBox(
|
|
// width: 40,
|
|
// child: Checkbox(
|
|
// value: currentPageIds.isNotEmpty &&
|
|
// currentPageIds.every(selectedEmployeeIds.contains),
|
|
// onChanged: (checked) {
|
|
// setState(() {
|
|
// if (checked == true) {
|
|
// selectedEmployeeIds.addAll(currentPageIds);
|
|
// } else {
|
|
// selectedEmployeeIds.removeAll(currentPageIds);
|
|
// }
|
|
// });
|
|
// },
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 3,
|
|
// child: Text(
|
|
// 'Name',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// if (localPolicyTypeId != '6' && localPolicyTypeId != '7')
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'UHID',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'Relationship',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'Date Of Birth',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'Gender',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'Mobile',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 5,
|
|
// child: Text(
|
|
// 'Email',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// 'Status',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// if (localTokenType != "pre" && (hasAnyEcardLink || hasModule))
|
|
// Expanded(
|
|
// flex: 3,
|
|
// child: Text(
|
|
// 'Action',
|
|
// textAlign: TextAlign.center,
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.black,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
//
|
|
// const SizedBox(height: 6),
|
|
//
|
|
// SingleChildScrollView(
|
|
// scrollDirection: Axis.vertical,
|
|
// child: Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: _paginatedData.mapIndexed((index, item) {
|
|
// return Container(
|
|
// // margin: const EdgeInsets.only(bottom: 8),
|
|
// padding:
|
|
// const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
|
|
// decoration: BoxDecoration(
|
|
// // color: Colors.white,
|
|
// border: Border(
|
|
// bottom: BorderSide(
|
|
// // color: Color(0xFFA1A1A1),
|
|
// color: Color(0xFFA9D9DE),
|
|
// width: 1,
|
|
// ),
|
|
// ),
|
|
// // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white,
|
|
// // borderRadius: BorderRadius.circular(6),
|
|
// ),
|
|
// child: Row(
|
|
// children: [
|
|
// if (localTokenType == 'post' &&
|
|
// localIsEcardBulkDownload == 1)
|
|
// SizedBox(
|
|
// width: 40,
|
|
// child: Checkbox(
|
|
// value: selectedEmployeeIds
|
|
// .contains(item['id']?.toString()),
|
|
// onChanged: (checked) {
|
|
// setState(() {
|
|
// final id = item['id']?.toString();
|
|
// if (id == null) return;
|
|
//
|
|
// if (checked == true) {
|
|
// selectedEmployeeIds.add(id);
|
|
// } else {
|
|
// selectedEmployeeIds.remove(id);
|
|
// }
|
|
// });
|
|
// },
|
|
// ),
|
|
// ),
|
|
//
|
|
// Expanded(
|
|
// flex: 3,
|
|
// child: Column(
|
|
// mainAxisAlignment: MainAxisAlignment.start,
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text(
|
|
// item['name'] ?? '-',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// Text(
|
|
// item['emp_code'] ?? '-',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF585757),
|
|
// fontWeight: FontWeight.w300,
|
|
// fontSize: 10),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// if (localPolicyTypeId != '6' &&
|
|
// localPolicyTypeId != '7')
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// item['uhid'] ?? '-',
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// "${item['relationship'] ?? ''}",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// "${item['formatted_dob'].replaceAll("/", "-") ?? ''}",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// "${item['gender'] ?? ''}",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Text(
|
|
// "${item['mobile'] ?? ''}",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 5,
|
|
// child: Text(
|
|
// "${item['email_corporate'] ?? ''}",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w400,
|
|
// fontSize: 12),
|
|
// ),
|
|
// ),
|
|
// Expanded(
|
|
// flex: 2,
|
|
// child: Container(
|
|
// padding: const EdgeInsets.symmetric(
|
|
// horizontal: 4, vertical: 4),
|
|
// decoration: BoxDecoration(
|
|
// color: getStatusColor(item['status'] ?? ''),
|
|
// // color: (item['emp_is_active'] == "1")
|
|
// // ? Color(0xFF7BD9B6)
|
|
// // : Color(0xFFFFA6A6),
|
|
// borderRadius: BorderRadius.circular(10),
|
|
// ),
|
|
// child: Align(
|
|
// alignment: Alignment.center,
|
|
// child: Text(
|
|
// _capitalize(item['status']),
|
|
// style: GoogleFonts.poppins(
|
|
// color: Color(0xFF000000),
|
|
// fontWeight: FontWeight.w500,
|
|
// fontSize: 12,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
//
|
|
// if (localTokenType != "pre" &&
|
|
// (hasAnyEcardLink || hasModule))
|
|
// Expanded(
|
|
// flex: 3,
|
|
// child: Builder(
|
|
// builder: (context) {
|
|
// final isSelf = item['relationship'] == 'Self';
|
|
// final hasEcard = item['ecard_download_link'] != null;
|
|
// final showEcard = isSelf && hasEcard;
|
|
// final showClaim = localTokenType == "post" && hasModule;
|
|
//
|
|
// if (!showEcard && !showClaim) {
|
|
// return SizedBox(); // No icon to show
|
|
// }
|
|
//
|
|
// return SizedBox(
|
|
// height: 40,
|
|
// child: Row(
|
|
// mainAxisAlignment: MainAxisAlignment.center,
|
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
|
// children: [
|
|
//
|
|
// /// --- eCard Button (Fixed Space) ---
|
|
// SizedBox(
|
|
// width: 40,
|
|
// height: 40,
|
|
// child: Visibility(
|
|
// visible: showEcard,
|
|
// maintainSize: true,
|
|
// maintainAnimation: true,
|
|
// maintainState: true,
|
|
// child: Tooltip(
|
|
// message: 'Download e-Card',
|
|
// child: MouseRegion(
|
|
// cursor: SystemMouseCursors.click,
|
|
// child: GestureDetector(
|
|
// onTap: () {
|
|
// getEcardDownload(
|
|
// item['emp_code'],
|
|
// item['employee_id'],
|
|
// item['client_policy_id'],
|
|
// item['policy_no'],
|
|
// );
|
|
// },
|
|
// child: Container(
|
|
// decoration: BoxDecoration(
|
|
// color: const Color(0xFFE6F5F6),
|
|
// borderRadius: BorderRadius.circular(8),
|
|
// ),
|
|
// padding: const EdgeInsets.all(6),
|
|
// child: Image.asset(
|
|
// 'assets/credit_card.png',
|
|
// fit: BoxFit.contain,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
//
|
|
// const SizedBox(width: 8),
|
|
//
|
|
// /// --- Claim Button (Fixed Space) ---
|
|
// SizedBox(
|
|
// width: 40,
|
|
// height: 40,
|
|
// child: Visibility(
|
|
// visible: showClaim,
|
|
// maintainSize: true,
|
|
// maintainAnimation: true,
|
|
// maintainState: true,
|
|
// child: Tooltip(
|
|
// message: 'View Claims',
|
|
// child: MouseRegion(
|
|
// cursor: SystemMouseCursors.click,
|
|
// child: GestureDetector(
|
|
// onTap: () {
|
|
// Navigator.push(
|
|
// context,
|
|
// MaterialPageRoute(
|
|
// builder: (context) => ClaimsPolicies(
|
|
// empCode: item['emp_code']!,
|
|
// ),
|
|
// ),
|
|
// );
|
|
// },
|
|
// child: Container(
|
|
// decoration: BoxDecoration(
|
|
// color: const Color(0xFFE6F5F6),
|
|
// borderRadius: BorderRadius.circular(8),
|
|
// ),
|
|
// padding: const EdgeInsets.all(6),
|
|
// child: Image.asset(
|
|
// 'assets/claim.png',
|
|
// fit: BoxFit.contain,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// },
|
|
// ),
|
|
// ),
|
|
//
|
|
// // (hasAnyEcardLink || hasModule))
|
|
// // Expanded(
|
|
// // flex: 3,
|
|
// // child: Row(
|
|
// // mainAxisAlignment: MainAxisAlignment.center,
|
|
// // children: [
|
|
// // if (item['ecard_download_link'] != null)
|
|
// // GestureDetector(
|
|
// // onTap: () {
|
|
// // _launchURL(item['ecard_download_link']);
|
|
// // },
|
|
// // child: Container(
|
|
// // height: 35,
|
|
// // width: 35,
|
|
// // decoration: BoxDecoration(
|
|
// // color: Color(0xFFE6F5F6),
|
|
// // borderRadius: BorderRadius.circular(8),
|
|
// // ),
|
|
// // child: Padding(
|
|
// // padding: EdgeInsets.all(
|
|
// // 6), // You can adjust this value
|
|
// // child: Image.asset(
|
|
// // 'assets/credit_card.png',
|
|
// // fit: BoxFit.contain,
|
|
// // ),
|
|
// // ),
|
|
// // ),
|
|
// // ),
|
|
// // SizedBox(
|
|
// // width: 10,
|
|
// // ),
|
|
// // if (localTokenType == "post" && hasModule)
|
|
// // GestureDetector(
|
|
// // onTap: () {
|
|
// // setState(() {
|
|
// // logDebug(
|
|
// // "policytabdata - ${item['emp_code']!}");
|
|
// // Navigator.push(
|
|
// // context,
|
|
// // MaterialPageRoute(
|
|
// // builder: (context) => hrDashboard(
|
|
// // selectedIndex: 3,
|
|
// // empCodeFromHrPolicy:
|
|
// // item['emp_code']!,
|
|
// // isHrcode: 1,
|
|
// // ),
|
|
// // ),
|
|
// // );
|
|
// // });
|
|
// // },
|
|
// // child: Container(
|
|
// // height: 35,
|
|
// // width: 35,
|
|
// // decoration: BoxDecoration(
|
|
// // color: Color(0xFFE6F5F6),
|
|
// // borderRadius: BorderRadius.circular(8),
|
|
// // ),
|
|
// // child: Padding(
|
|
// // padding: EdgeInsets.all(
|
|
// // 6), // You can adjust this value
|
|
// // child: Image.asset(
|
|
// // 'assets/claim.png',
|
|
// // fit: BoxFit.contain,
|
|
// // ),
|
|
// // ),
|
|
// // ),
|
|
// // ),
|
|
// // ],
|
|
// // ),
|
|
// // ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }).toList(),
|
|
// ),
|
|
// ),
|
|
// // ),
|
|
// _buildPagination(context)
|
|
// // Row(
|
|
// // mainAxisAlignment: MainAxisAlignment.end,
|
|
// // children: [
|
|
// // Padding(
|
|
// // padding: const EdgeInsets.symmetric(vertical: 12),
|
|
// // child: Row(
|
|
// // mainAxisAlignment: MainAxisAlignment.center,
|
|
// // children: [
|
|
// // DropdownButton<int>(
|
|
// // value: _rowsPerPage,
|
|
// // items: [5, 10, 15, 20, 50].map((int value) {
|
|
// // return DropdownMenuItem<int>(
|
|
// // value: value,
|
|
// // child: Text(
|
|
// // ' $value ',
|
|
// // style: GoogleFonts.poppins(fontSize: 15),
|
|
// // ),
|
|
// // );
|
|
// // }).toList(),
|
|
// // onChanged: (newValue) {
|
|
// // setState(() {
|
|
// // _rowsPerPage = newValue!;
|
|
// // _currentPage =
|
|
// // 1; // Reset to first page when rows per page changes
|
|
// // });
|
|
// // },
|
|
// // ),
|
|
// // IconButton(
|
|
// // onPressed: _currentPage > 1
|
|
// // ? () {
|
|
// // setState(() {
|
|
// // _currentPage--;
|
|
// // });
|
|
// // }
|
|
// // : null,
|
|
// // icon: Icon(Icons.chevron_left),
|
|
// // ),
|
|
// // for (int i = 1;
|
|
// // i <= (filteredData.length / _rowsPerPage).ceil();
|
|
// // i++)
|
|
// // Padding(
|
|
// // padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
// // child: ElevatedButton(
|
|
// // style: ElevatedButton.styleFrom(
|
|
// // backgroundColor: _currentPage == i
|
|
// // ? Color(0xFF00A6A6)
|
|
// // : Colors.grey[300],
|
|
// // foregroundColor:
|
|
// // _currentPage == i ? Colors.white : Colors.black,
|
|
// // minimumSize: Size(36, 36),
|
|
// // padding: EdgeInsets.zero,
|
|
// // ),
|
|
// // onPressed: () {
|
|
// // setState(() {
|
|
// // _currentPage = i;
|
|
// // });
|
|
// // },
|
|
// // child: Text(i.toString()),
|
|
// // ),
|
|
// // ),
|
|
// // IconButton(
|
|
// // onPressed: _currentPage <
|
|
// // (filteredData.length / _rowsPerPage).ceil()
|
|
// // ? () {
|
|
// // setState(() {
|
|
// // _currentPage++;
|
|
// // });
|
|
// // }
|
|
// // : null,
|
|
// // icon: Icon(Icons.chevron_right),
|
|
// // ),
|
|
// // ],
|
|
// // ),
|
|
// // ),
|
|
// // ],
|
|
// // ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
Widget _buildPagination(BuildContext context) {
|
|
// 1. Calculate the range of entries being shown
|
|
final totalItems = filteredData.length;
|
|
final int startEntry =
|
|
totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1;
|
|
int endEntry = _currentPage * _rowsPerPage;
|
|
if (endEntry > totalItems) endEntry = totalItems;
|
|
|
|
final totalPages = (filteredData.length / _rowsPerPage).ceil();
|
|
const visiblePageCount = 5;
|
|
|
|
List<int> getVisiblePages() {
|
|
if (totalPages <= visiblePageCount) {
|
|
return List.generate(totalPages, (i) => i + 1);
|
|
}
|
|
|
|
if (_currentPage <= 3) {
|
|
return [1, 2, 3, 4, 5];
|
|
}
|
|
if (_currentPage >= totalPages - 2) {
|
|
return [
|
|
totalPages - 4,
|
|
totalPages - 3,
|
|
totalPages - 2,
|
|
totalPages - 1,
|
|
totalPages
|
|
];
|
|
}
|
|
return [
|
|
_currentPage - 2,
|
|
_currentPage - 1,
|
|
_currentPage,
|
|
_currentPage + 1,
|
|
_currentPage + 2,
|
|
];
|
|
}
|
|
|
|
List<int> visiblePages = getVisiblePages();
|
|
|
|
return Padding(
|
|
// Match this horizontal padding (16) to your Table Header padding for perfect alignment
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment
|
|
.spaceBetween, // Pushes text to left, buttons to right
|
|
children: [
|
|
// --- LEFT SIDE: Showing Text ---
|
|
Text(
|
|
"Showing $startEntry to $endEntry of $totalItems entries",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: const Color(0xFF585757),
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
|
|
// --- RIGHT SIDE: Controls ---
|
|
Row(
|
|
children: [
|
|
// Dropdown for rows per page
|
|
DropdownButton<int>(
|
|
value: _rowsPerPage,
|
|
focusColor: Colors
|
|
.transparent, // Fix: Removes the grey/blue highlight on change
|
|
items: [5, 10, 15, 20, 50].map((int value) {
|
|
return DropdownMenuItem<int>(
|
|
value: value,
|
|
child: Text(' $value ',
|
|
style: GoogleFonts.poppins(fontSize: 15)),
|
|
);
|
|
}).toList(),
|
|
onChanged: (newValue) {
|
|
setState(() {
|
|
_rowsPerPage = newValue!;
|
|
_currentPage = 1;
|
|
});
|
|
},
|
|
),
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
// Previous button
|
|
IconButton(
|
|
tooltip: 'Previous Page',
|
|
onPressed: _currentPage > 1
|
|
? () => setState(() => _currentPage--)
|
|
: null,
|
|
icon: const Icon(Icons.chevron_left),
|
|
),
|
|
|
|
// First page + left ellipsis
|
|
if (!visiblePages.contains(1))
|
|
Row(children: [
|
|
_buildPageButton(1),
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
|
child: Text("..."),
|
|
),
|
|
]),
|
|
|
|
// Visible page buttons
|
|
for (int page in visiblePages) _buildPageButton(page),
|
|
|
|
// Right ellipsis + last page
|
|
if (!visiblePages.contains(totalPages) && totalPages > 0)
|
|
Row(children: [
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
|
child: Text("..."),
|
|
),
|
|
_buildPageButton(totalPages),
|
|
]),
|
|
|
|
// Next button
|
|
IconButton(
|
|
onPressed: _currentPage < totalPages
|
|
? () => setState(() => _currentPage++)
|
|
: null,
|
|
icon: const Icon(Icons.chevron_right),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPageButton(int page) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
_currentPage == page ? const Color(0xFF00A6A6) : Colors.grey[300],
|
|
foregroundColor: _currentPage == page ? Colors.white : Colors.black,
|
|
minimumSize: const Size(36, 36),
|
|
padding: EdgeInsets.zero,
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
_currentPage = page;
|
|
});
|
|
},
|
|
child: Text(page.toString()),
|
|
),
|
|
);
|
|
}
|
|
|
|
static final _dataBold = GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
color: Color(0xFF000000),
|
|
);
|
|
|
|
static final _dataBoldStatus = GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.white,
|
|
);
|
|
|
|
static final _dataSub = GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w300,
|
|
color: Color(0xFF585757),
|
|
);
|
|
|
|
static final _dataColorSub = GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w400,
|
|
color: Color(0xFFFF731C),
|
|
);
|
|
}
|
|
|
|
class _CDHeaderDelegate extends SliverPersistentHeaderDelegate {
|
|
final bool showUHID;
|
|
final bool showLoggedIn;
|
|
final bool showAction;
|
|
|
|
_CDHeaderDelegate({
|
|
required this.showUHID,
|
|
required this.showLoggedIn,
|
|
required this.showAction,
|
|
});
|
|
|
|
@override
|
|
double get minExtent => 55;
|
|
|
|
@override
|
|
double get maxExtent => 55;
|
|
|
|
@override
|
|
Widget build(
|
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
|
return Container(
|
|
color: const Color(0xFFD7E9EB),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
alignment: Alignment.centerLeft,
|
|
child: Row(
|
|
children: [
|
|
_headerCell('Name', 3),
|
|
if (showUHID) _headerCell('UHID', 2),
|
|
_headerCell('Relationship', 2),
|
|
_headerCell('Date Of Birth', 2),
|
|
_headerCell('Gender', 2),
|
|
_headerCell('Mobile', 2),
|
|
_headerCell('Email', 5),
|
|
if (showLoggedIn) _headerCell('Logged In', 2),
|
|
if (showLoggedIn) const SizedBox(width: 8),
|
|
_headerCell('Status', 2),
|
|
if (showAction) _headerCell('Action', 3, center: true),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _headerCell(String text, int flex, {bool center = false}) {
|
|
return Expanded(
|
|
flex: flex,
|
|
child: Text(
|
|
text,
|
|
textAlign: center ? TextAlign.center : TextAlign.left,
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
|
|
true;
|
|
}
|
|
|
|
class _PolicyTermsDialog extends StatelessWidget {
|
|
final Map<String, String> terms;
|
|
|
|
const _PolicyTermsDialog({required this.terms});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final entries = terms.entries.toList();
|
|
final maxHeight = MediaQuery.of(context).size.height * 0.65;
|
|
final cappedMaxHeight = maxHeight.clamp(320.0, 560.0);
|
|
final contentHeight =
|
|
(entries.length * 88.0).clamp(120.0, cappedMaxHeight);
|
|
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
title: Text(
|
|
'Policy Terms',
|
|
style: GoogleFonts.poppins(fontWeight: FontWeight.w600, fontSize: 18),
|
|
),
|
|
content: SizedBox(
|
|
width: 640,
|
|
height: contentHeight,
|
|
child: Scrollbar(
|
|
thumbVisibility: entries.length > 4,
|
|
child: ListView.separated(
|
|
itemCount: entries.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
|
itemBuilder: (context, index) {
|
|
final entry = entries[index];
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF5F5F5),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: const Color(0xFFE8E8E8)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
entry.key,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: const Color(0xFF009195),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
entry.value,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF009195),
|
|
),
|
|
child: Text('Close', style: GoogleFonts.poppins(color: Colors.white)),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ReminderMailConfigDialog extends StatefulWidget {
|
|
final String clientId;
|
|
final String clientPolicyId;
|
|
final String clientBranchId;
|
|
final String token;
|
|
final ApiService apiService;
|
|
final String defaultSubject;
|
|
final String defaultHtmlBody;
|
|
|
|
const _ReminderMailConfigDialog({
|
|
required this.clientId,
|
|
required this.clientPolicyId,
|
|
required this.clientBranchId,
|
|
required this.token,
|
|
required this.apiService,
|
|
required this.defaultSubject,
|
|
required this.defaultHtmlBody,
|
|
});
|
|
|
|
@override
|
|
State<_ReminderMailConfigDialog> createState() =>
|
|
_ReminderMailConfigDialogState();
|
|
}
|
|
|
|
class _ReminderMailConfigDialogState extends State<_ReminderMailConfigDialog> {
|
|
static const _frequencies = <String, String>{
|
|
'daily': 'Daily',
|
|
'weekly': 'Weekly',
|
|
'monthly': 'Monthly',
|
|
'custom': 'Custom',
|
|
'working_days': 'Working Days',
|
|
};
|
|
|
|
bool _isLoadingConfig = true;
|
|
bool _isSaving = false;
|
|
int? _configId;
|
|
String _frequency = 'daily';
|
|
bool _isEnabled = true;
|
|
String _reminderDaysText = '';
|
|
final Set<String> _selectedWorkingDays = {};
|
|
List<Map<String, dynamic>> _workingDayOptions = [];
|
|
late final TextEditingController _reminderDaysController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_reminderDaysController = TextEditingController();
|
|
_loadConfig();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_reminderDaysController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadConfig() async {
|
|
setState(() => _isLoadingConfig = true);
|
|
try {
|
|
final response = await widget.apiService.getReminderMailConfigApi(
|
|
widget.clientPolicyId,
|
|
widget.token,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
final ok = response['status'] == true || response['status'] == 'success';
|
|
if (ok) {
|
|
_workingDayOptions = _parseWorkingDayOptions(
|
|
response['working_day_options'],
|
|
);
|
|
_applyConfig(response['data']);
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Failed to load reminder config',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logDebug('Reminder config load failed: $e');
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Failed to load reminder configuration',
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isLoadingConfig = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
List<Map<String, dynamic>> _parseWorkingDayOptions(dynamic raw) {
|
|
if (raw is! List) return _defaultWorkingDayOptions();
|
|
return raw
|
|
.whereType<Map>()
|
|
.map((item) => Map<String, dynamic>.from(item))
|
|
.toList();
|
|
}
|
|
|
|
List<Map<String, dynamic>> _defaultWorkingDayOptions() {
|
|
return [
|
|
{'value': 1, 'label': 'Mon', 'key': 'mon'},
|
|
{'value': 2, 'label': 'Tue', 'key': 'tue'},
|
|
{'value': 3, 'label': 'Wed', 'key': 'wed'},
|
|
{'value': 4, 'label': 'Thu', 'key': 'thu'},
|
|
{'value': 5, 'label': 'Fri', 'key': 'fri'},
|
|
{'value': 6, 'label': 'Sat', 'key': 'sat'},
|
|
{'value': 7, 'label': 'Sun', 'key': 'sun'},
|
|
];
|
|
}
|
|
|
|
void _applyConfig(dynamic data) {
|
|
_configId = null;
|
|
_frequency = 'daily';
|
|
_isEnabled = true;
|
|
_reminderDaysText = '';
|
|
_reminderDaysController.clear();
|
|
_selectedWorkingDays.clear();
|
|
|
|
if (data is! Map) return;
|
|
|
|
final config = Map<String, dynamic>.from(data);
|
|
_configId = int.tryParse(config['id']?.toString() ?? '');
|
|
_frequency = config['frequency']?.toString() ?? 'daily';
|
|
_isEnabled = config['is_enabled']?.toString() != '0';
|
|
|
|
if (_frequency == 'working_days') {
|
|
final labels = config['working_day_labels'];
|
|
if (labels is List && labels.isNotEmpty) {
|
|
_selectedWorkingDays.addAll(labels.map((e) => e.toString()));
|
|
} else {
|
|
_applyWorkingDaysFromReminderDays(
|
|
config['reminder_days']?.toString() ?? '',
|
|
);
|
|
}
|
|
} else if (_frequency != 'daily') {
|
|
_reminderDaysText = config['reminder_days']?.toString() ?? '';
|
|
_reminderDaysController.text = _reminderDaysText;
|
|
}
|
|
}
|
|
|
|
void _applyWorkingDaysFromReminderDays(String reminderDays) {
|
|
if (reminderDays.isEmpty) return;
|
|
|
|
final options = _workingDayOptions.isNotEmpty
|
|
? _workingDayOptions
|
|
: _defaultWorkingDayOptions();
|
|
final valueToLabel = <String, String>{
|
|
for (final option in options)
|
|
option['value'].toString(): option['label'].toString(),
|
|
};
|
|
|
|
for (final part in reminderDays.split(',')) {
|
|
final token = part.trim();
|
|
if (token.isEmpty) continue;
|
|
|
|
if (RegExp(r'^\d+$').hasMatch(token)) {
|
|
final normalized = token == '0' ? '7' : token;
|
|
final label = valueToLabel[normalized];
|
|
if (label != null) {
|
|
_selectedWorkingDays.add(label);
|
|
}
|
|
} else {
|
|
final match = options.firstWhere(
|
|
(option) =>
|
|
option['label'].toString().toLowerCase() == token.toLowerCase() ||
|
|
option['key'].toString().toLowerCase() == token.toLowerCase(),
|
|
orElse: () => {},
|
|
);
|
|
if (match.isNotEmpty) {
|
|
_selectedWorkingDays.add(match['label'].toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String? _buildReminderDays() {
|
|
switch (_frequency) {
|
|
case 'daily':
|
|
return null;
|
|
case 'working_days':
|
|
if (_selectedWorkingDays.isEmpty) return null;
|
|
return _selectedWorkingDays.join(',');
|
|
default:
|
|
final value = _reminderDaysText.trim();
|
|
return value.isEmpty ? null : value;
|
|
}
|
|
}
|
|
|
|
String? _validateBeforeSave() {
|
|
if (_frequency == 'working_days' && _selectedWorkingDays.isEmpty) {
|
|
return 'Select at least one working day';
|
|
}
|
|
if (_frequency != 'daily' &&
|
|
_frequency != 'working_days' &&
|
|
_reminderDaysText.trim().isEmpty) {
|
|
return 'Reminder days are required for $_frequency frequency';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<int?> _resolveHrId() async {
|
|
final tokenService = TokenStorageService();
|
|
final hrId = await tokenService.readValue('enrollmentHrId') ??
|
|
await tokenService.readValue('empHrId');
|
|
return int.tryParse(hrId?.toString() ?? '');
|
|
}
|
|
|
|
Future<void> _saveConfig() async {
|
|
final validationError = _validateBeforeSave();
|
|
if (validationError != null) {
|
|
ToastHelper.showWarningToast(context, validationError);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSaving = true);
|
|
try {
|
|
final policyId = int.tryParse(widget.clientPolicyId);
|
|
if (policyId == null) {
|
|
ToastHelper.showErrorToast(context, 'Invalid client policy id');
|
|
return;
|
|
}
|
|
|
|
final payload = <String, dynamic>{
|
|
'client_policy_id': policyId,
|
|
'frequency': _frequency,
|
|
'is_enabled': _isEnabled ? 1 : 0,
|
|
};
|
|
|
|
final reminderDays = _buildReminderDays();
|
|
if (reminderDays != null) {
|
|
payload['reminder_days'] = reminderDays;
|
|
}
|
|
|
|
final hrId = await _resolveHrId();
|
|
if (hrId != null) {
|
|
payload['hr_id'] = hrId;
|
|
}
|
|
|
|
if (_configId != null) {
|
|
payload['id'] = _configId;
|
|
}
|
|
|
|
final response = await widget.apiService.saveReminderMailConfigApi(
|
|
widget.token,
|
|
payload,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
final ok = response['status'] == true || response['status'] == 'success';
|
|
if (ok) {
|
|
final data = response['data'];
|
|
if (data is Map) {
|
|
_applyConfig(data);
|
|
}
|
|
ToastHelper.showSuccessToast(
|
|
context,
|
|
response['message']?.toString() ??
|
|
'Reminder mail configuration saved successfully',
|
|
);
|
|
Navigator.pop(context);
|
|
} else {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
response['message']?.toString() ?? 'Failed to save configuration',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logDebug('Reminder config save failed: $e');
|
|
if (mounted) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Failed to save reminder configuration',
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isSaving = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _buildDaysField() {
|
|
if (_frequency == 'daily') {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
if (_frequency == 'working_days') {
|
|
final options = _workingDayOptions.isNotEmpty
|
|
? _workingDayOptions
|
|
: _defaultWorkingDayOptions();
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Working Days',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: options.map((option) {
|
|
final label = option['label'].toString();
|
|
final selected = _selectedWorkingDays.contains(label);
|
|
return FilterChip(
|
|
label: Text(label, style: GoogleFonts.poppins(fontSize: 12)),
|
|
selected: selected,
|
|
onSelected: (value) {
|
|
setState(() {
|
|
if (value) {
|
|
_selectedWorkingDays.add(label);
|
|
} else {
|
|
_selectedWorkingDays.remove(label);
|
|
}
|
|
});
|
|
},
|
|
selectedColor: const Color(0xFFC5F2F4),
|
|
checkmarkColor: const Color(0xFF009195),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
final helperText = switch (_frequency) {
|
|
'weekly' => 'Weekday numbers 0-6 (Sun-Sat), comma-separated. Example: 1,3,5',
|
|
'monthly' => 'Days of month 1-31, comma-separated. Example: 1,15,28',
|
|
'custom' => 'Custom days of month 1-31, comma-separated. Example: 5,10,20',
|
|
_ => '',
|
|
};
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Reminder Days',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
TextField(
|
|
controller: _reminderDaysController,
|
|
onChanged: (value) => _reminderDaysText = value,
|
|
style: GoogleFonts.poppins(fontSize: 14),
|
|
decoration: InputDecoration(
|
|
hintText: helperText,
|
|
filled: true,
|
|
fillColor: const Color(0xFFF5F5F5),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 10,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
helperText,
|
|
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black45),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<void> _openTemplateEditor() async {
|
|
await showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (dialogContext) {
|
|
return ReminderEmailTemplateDialog(
|
|
clientId: widget.clientId,
|
|
clientPolicyId: widget.clientPolicyId,
|
|
clientBranchId: widget.clientBranchId,
|
|
token: widget.token,
|
|
apiService: widget.apiService,
|
|
defaultSubject: widget.defaultSubject,
|
|
defaultHtmlBody: widget.defaultHtmlBody,
|
|
mode: ReminderEmailTemplateMode.config,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isBusy = _isSaving;
|
|
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
title: Text(
|
|
'Email Reminder Config',
|
|
style: GoogleFonts.poppins(fontWeight: FontWeight.w600, fontSize: 18),
|
|
),
|
|
content: SizedBox(
|
|
width: 560,
|
|
child: _isLoadingConfig
|
|
? const SizedBox(
|
|
height: 180,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
)
|
|
: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Configure the schedule for automated enrollment reminder emails.',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SwitchListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: Text(
|
|
'Enable scheduled reminders',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
value: _isEnabled,
|
|
activeColor: const Color(0xFF009195),
|
|
onChanged: isBusy
|
|
? null
|
|
: (value) => setState(() => _isEnabled = value),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Frequency',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
DropdownButtonFormField<String>(
|
|
value: _frequency,
|
|
isExpanded: true,
|
|
decoration: InputDecoration(
|
|
filled: true,
|
|
fillColor: const Color(0xFFF5F5F5),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 10,
|
|
),
|
|
),
|
|
items: _frequencies.entries
|
|
.map(
|
|
(entry) => DropdownMenuItem(
|
|
value: entry.key,
|
|
child: Text(
|
|
entry.value,
|
|
style: GoogleFonts.poppins(fontSize: 14),
|
|
),
|
|
),
|
|
)
|
|
.toList(),
|
|
onChanged: isBusy
|
|
? null
|
|
: (value) {
|
|
if (value == null) return;
|
|
setState(() {
|
|
_frequency = value;
|
|
_reminderDaysText = '';
|
|
_reminderDaysController.clear();
|
|
_selectedWorkingDays.clear();
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 14),
|
|
_buildDaysField(),
|
|
const SizedBox(height: 20),
|
|
// OutlinedButton.icon(
|
|
// onPressed: isBusy ? null : _openTemplateEditor,
|
|
// icon: const Icon(Icons.edit_outlined, size: 18),
|
|
// label: Text(
|
|
// 'Edit Email Template',
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: 13,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// style: OutlinedButton.styleFrom(
|
|
// foregroundColor: const Color(0xFF009195),
|
|
// side: const BorderSide(color: Color(0xFF009195)),
|
|
// padding: const EdgeInsets.symmetric(
|
|
// horizontal: 16,
|
|
// vertical: 12,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: isBusy ? null : () => Navigator.pop(context),
|
|
child: Text('Cancel', style: GoogleFonts.poppins(color: Colors.black54)),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: isBusy || _isLoadingConfig ? null : _saveConfig,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF009195),
|
|
),
|
|
child: _isSaving
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: Text('Save', style: GoogleFonts.poppins(color: Colors.white)),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|