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 createState() => _HrPolicyDetailsState(); } class _HrPolicyDetailsState extends State 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> getEmpDependenceByClintId = []; List> getCDPolicies = []; bool isLoading = false; bool _isLoading = false; bool _isSendingReminder = false; bool _isLoadingPolicyTerms = false; // dynamic clintID; late TabController _tabController; // List dataPolicy = []; List reversedDataPolicy = []; List> originalData = []; // Original data source List> 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 selectedEmployeeIds = {}; /// ids shown in the current page (for header checkbox) List currentPageIds = []; List 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 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 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 _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 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? 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>.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 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? 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>.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 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 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 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 _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 row) { final raw = localTokenType == 'pre' ? (row['emp_status'] ?? row['status']) : row['status']; return raw?.toString().toLowerCase().trim() ?? ''; } String? _getRowStatusRaw(Map 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 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 row) => _getLoggedInValue(row) == 'yes'; bool _isSelfRow(Map row) => row['relationship']?.toString().toLowerCase().trim() == 'self'; String _getLoggedInDisplayLabel(Map row) => _getLoggedInValue(row) == 'yes' ? 'Yes' : 'No'; Color _getLoggedInChipColor(Map row) => _getLoggedInValue(row) == 'yes' ? const Color(0xFFBDF9D9) : const Color(0xFFE8EAF6); bool _matchesPreStatusFilter(Map 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 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> 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> data) { List> 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 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 _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 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( 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 _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>.from(response['data'] ?? []); Map? 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 = {}; 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( 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 _openReminderConfigDialog() async { final clientId = localClientId ?? widget.ClientId; final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId; final clientBranchId = localClientBranchId ?? widget.clientBranchId; final token = localToken ?? widget.Token; await showDialog( 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 _openReminderTemplateDialog() async { final clientId = localClientId ?? widget.ClientId; final clientPolicyId = localClientPolicyId ?? widget.ClientPoliyId; final clientBranchId = localClientBranchId ?? widget.clientBranchId; final token = localToken ?? widget.Token; await showDialog( 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 _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 _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 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 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 = >[ {'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 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( 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() // .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( // // value: _rowsPerPage, // // items: [5, 10, 15, 20, 50].map((int value) { // // return DropdownMenuItem( // // 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 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 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( value: _rowsPerPage, focusColor: Colors .transparent, // Fix: Removes the grey/blue highlight on change items: [5, 10, 15, 20, 50].map((int value) { return DropdownMenuItem( 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 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 = { '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 _selectedWorkingDays = {}; List> _workingDayOptions = []; late final TextEditingController _reminderDaysController; @override void initState() { super.initState(); _reminderDaysController = TextEditingController(); _loadConfig(); } @override void dispose() { _reminderDaysController.dispose(); super.dispose(); } Future _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> _parseWorkingDayOptions(dynamic raw) { if (raw is! List) return _defaultWorkingDayOptions(); return raw .whereType() .map((item) => Map.from(item)) .toList(); } List> _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.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 = { 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 _resolveHrId() async { final tokenService = TokenStorageService(); final hrId = await tokenService.readValue('enrollmentHrId') ?? await tokenService.readValue('empHrId'); return int.tryParse(hrId?.toString() ?? ''); } Future _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 = { '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 _openTemplateEditor() async { await showDialog( 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( 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)), ), ], ); } }