import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:nhancepolicy/responsive.dart'; import 'package:nhancepolicy/service/api_service.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/activePolicies.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart'; import 'package:nhancepolicy/presentation/claims.dart'; import 'package:nhancepolicy/presentation/postFileUpload.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart'; import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:nhancepolicy/service/_ResponsiveGridConfig.dart'; import 'package:http/http.dart' as http; import 'package:universal_html/html.dart' as html; import 'package:intl/intl.dart'; import '../config/environment.dart'; import '../customAppBar/base_layout.dart'; import '../customAppBar/toastHelper.dart'; import '../service/secure_pop_scope.dart'; import 'hrPolicyDetails.dart'; import 'package:nhancepolicy/logger.dart'; class policies extends StatefulWidget { const policies({Key? key}) : super(key: key); @override State createState() => _policiesState(); } class _policiesState extends State with SingleTickerProviderStateMixin { late ApiService apiService; bool isLoading = false; int isHrcode = 0; String? _postPreToken = ''; dynamic enrollmentClient_id; dynamic policy_name; dynamic getPreCardArrays = []; dynamic getPostCardArrays = []; dynamic empClientBranchId; dynamic empHrId; int stausVal = 1; int selectedIndex = 1; dynamic enrollmentEmpClientBranchId; dynamic enrollmentHrId; dynamic empClientId; String empCodeFromHrPolcy = ''; List> openForEnrollmentList = []; List> activePoliciesList = []; final tokenService = TokenStorageService(); List postModules = []; List enrollmentModules = []; @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here checkToken(); // empCodeFromHrPolcy = widget.empCodeFromHrPolicy; // _tabController = TabController(length: 4, vsync: this); // _tabController.addListener(() { // setState(() { // selectedIndex = _tabController.index; // }); // }); // Future.delayed(Duration(seconds: 3), () { // setState(() { // isLoading = false; // }); // }); } @override void dispose() { super.dispose(); } checkToken() async { // enrollToken = prefs.getString('pre_enrollment_data'); // _postToken = prefs.getString('post_enrollment_data'); // Get the current token final token = await tokenService.getCurrentToken(); logDebug('token - $token'); logDebug('token check in'); if ((token != null && token!.isNotEmpty)) { logDebug('token check done'); _loadToken(); } else { logDebug('token check reject'); ToastHelper.showErrorToast(context, 'Session Out'); Navigator.pushReplacementNamed(context, 'hrLogin'); } } // Future _loadToken() async { // final enrollmentRaw = await tokenService.readValue('enrollmentAllowed_modules'); // "[1]" // final postRaw = await tokenService.readValue('empAllowed_modules'); // "[2,3,4]" // // // βœ… Decode safely // enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty // ? List.from(jsonDecode(enrollmentRaw)) // : []; // // postModules = postRaw != null && postRaw.isNotEmpty // ? List.from(jsonDecode(postRaw)) // : []; // // logDebug('enrollmentModules $enrollmentModules'); // logDebug('postModules $postModules'); // // _postPreToken = await tokenService.getCurrentToken(); // logDebug(_postPreToken); // // if (enrollmentModules.contains(1)) { // enrollmentClient_id = await tokenService.readValue('enrollmentClient_id'); // enrollmentEmpClientBranchId = // await tokenService.readValue('enrollmentEmpClientBranchId'); // enrollmentHrId = await tokenService.readValue('enrollmentHrId'); // // await getPreCashDepositDetails(enrollmentEmpClientBranchId, // enrollmentClient_id, enrollmentHrId, _postPreToken); // } // // if (postModules.contains(2)) { // empClientId = await tokenService.readValue('empClientId'); // empClientBranchId = await tokenService.readValue('empClientBranchId'); // empHrId = await tokenService.readValue('empHrId'); // // await getPostCashDepositDetails(empClientBranchId, empClientId, empHrId, _postPreToken); // } // // } Future _loadToken() async { setState(() { isLoading = true; // πŸ”₯ START LOADER HERE }); try { final enrollmentRaw = await tokenService.readValue('enrollmentAllowed_modules'); final postRaw = await tokenService.readValue('empAllowed_modules'); enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty ? List.from(jsonDecode(enrollmentRaw)) : []; postModules = postRaw != null && postRaw.isNotEmpty ? List.from(jsonDecode(postRaw)) : []; _postPreToken = await tokenService.getCurrentToken(); List apiCalls = []; /// πŸ‘‡ Add APIs dynamically if (enrollmentModules.contains(1)) { enrollmentClient_id = await tokenService.readValue('enrollmentClient_id'); enrollmentEmpClientBranchId = await tokenService.readValue('enrollmentEmpClientBranchId'); enrollmentHrId = await tokenService.readValue('enrollmentHrId'); apiCalls.add( getPreCashDepositDetails( enrollmentEmpClientBranchId, enrollmentClient_id, enrollmentHrId, _postPreToken, ), ); } if (postModules.contains(2)) { empClientId = await tokenService.readValue('empClientId'); empClientBranchId = await tokenService.readValue('empClientBranchId'); empHrId = await tokenService.readValue('empHrId'); apiCalls.add( getPostCashDepositDetails( empClientBranchId, empClientId, empHrId, _postPreToken, ), ); } /// πŸ”₯ WAIT FOR ALL APIs await Future.wait(apiCalls); } catch (e) { logDebug("Error in _loadToken: $e"); } finally { if (mounted) { setState(() { isLoading = false; // πŸ”₯ STOP LOADER ONLY ONCE }); } } } Future getPreCashDepositDetails(enrollmentEmpClientBranchId, enrollmentClient_id, enrollmentHrId, _postPreToken) async { logDebug('IN'); logDebug("clintBranchId -$enrollmentEmpClientBranchId"); logDebug("clintID -$enrollmentClient_id"); logDebug("hr_id -$enrollmentHrId"); logDebug("token -$_postPreToken"); // setState(() { // _isLoading = true; // }); try { if (enrollmentEmpClientBranchId == null || enrollmentClient_id == null) { return; } final response = await apiService.getCashDepositDetailsToApi( enrollmentClient_id!, enrollmentEmpClientBranchId!, enrollmentHrId, _postPreToken); // final response = await apiService.getCashDepositDetailsToApi( // clintID!, clintBranchId!, hr_id, token); logDebug('IN1'); if (response['status'] == 'success') { setState(() { logDebug('response'); logDebug(response['data']); getPreCardArrays = List>.from(response['data']); logDebug('getPreCardArrays'); logDebug(getPreCardArrays); openForEnrollmentList = getPreCardArrays; }); logDebug('IN2'); } else { logDebug('API request failed with status'); } } catch (e) { logDebug('Exception occurred: $e'); } } Future getPostCashDepositDetails( empClientBranchId, empClientId, empHrId, _postPreToken) async { logDebug('IN'); logDebug("clintBranchId -$empClientBranchId"); logDebug("clintID -$empClientId"); logDebug("hr_id -$empHrId"); logDebug("token -$_postPreToken"); // setState(() { // _isLoading = true; // }); try { if (empClientBranchId == null || empClientId == null) { return; } final response = await apiService.getActiveCashDepositDetailsToApi( empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal); // final response = await apiService.getCashDepositDetailsToApi( // clintID!, clintBranchId!, hr_id, token); logDebug('IN1'); if (response['status'] == 'success') { setState(() { logDebug('response'); logDebug(response['data']); getPostCardArrays = List>.from(response['data']); logDebug('getPostCardArrays'); logDebug(getPostCardArrays); activePoliciesList = getPostCardArrays; }); logDebug('IN2'); } else { logDebug('API request failed with status'); setState(() { activePoliciesList = []; }); logDebug('API request failed with status'); } } catch (e) { logDebug('Exception occurred: $e'); } } Future _showLogoutDialog() async { return await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text("Confirm Logout"), content: Text("Do you want to logout?"), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text("Cancel"), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text("Logout"), ), ], ), ) ?? false; } Future getEcardBulkDownload(clientPolicyId) async { try { logDebug('10'); empHrId = await tokenService.readValue('empHrId'); final response = await apiService.getEcardBulkDownloadApi( clientPolicyId, empHrId, '', _postPreToken!); 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: GoogleFonts.poppins( 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 Widget build(BuildContext context) { return BaseLayout( child: SecurePopScope( child: buildPoliciesBody( openEnrollment: openForEnrollmentList, activePolicies: activePoliciesList, ), ), ); } Widget buildPoliciesBody({ required List> openEnrollment, required List> activePolicies, }) { return 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 ), ) : Scaffold( body: SingleChildScrollView( // padding: const EdgeInsets.all(20), child: Container( color: const Color(0xFFF5F7F7), // πŸ‘ˆ same light grey as dashboard // padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // IconButton( // onPressed: () { // _showLogoutDialog(); // }, // icon: const Icon( // Icons.arrow_back_ios, // size: 20, // color: Colors.black, // ), // padding: EdgeInsets.zero, // constraints: const BoxConstraints(), // ), const SizedBox(width: 5), Text( 'Policies', style: GoogleFonts.poppins( fontSize: 22, fontWeight: FontWeight.w500, color: Colors.black, ), ), ], ), if (enrollmentModules.contains(1)) ...[ SizedBox(height: 15), Container( width: double.infinity, // constraints: const BoxConstraints( // minHeight: 180, // ), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), ), child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Open for Enrollment', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600), ), SizedBox(height: 14), /// βœ… SCROLLABLE AREA openEnrollment.isEmpty ? _EmptyBox('No policies open for enrollment') : _PolicyGrid( policies: openEnrollment, isEnrollment: true, ), ], ), )), ], if (postModules.contains(2)) ...[ SizedBox(height: 20), Container( width: double.infinity, // constraints: const BoxConstraints( // minHeight: 180, // ), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// HEADER Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Active Policies', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600), ), _ActiveExpiredToggle( selectedIndex: selectedIndex, onChange: (value) async { setState(() { selectedIndex = value; stausVal = value == 1 ? 1 : 0; }); await getPostCashDepositDetails( empClientBranchId, empClientId, empHrId, _postPreToken, ); }, ), ], ), const SizedBox(height: 14), /// βœ… SCROLLABLE GRID activePolicies.isEmpty ? stausVal == 0 ? _EmptyBox( 'You don’t have any expired policies at the moment.') : _EmptyBox('No active policies found') : _PolicyGrid( policies: activePolicies, isEnrollment: false, onBulkDownload: getEcardBulkDownload, ), const SizedBox(height: 8), ], ), ), ] ], ), )), ); } } class ResponsiveGridConfig { final int crossAxisCount; final double childAspectRatio; const ResponsiveGridConfig( this.crossAxisCount, this.childAspectRatio, ); } class _PolicyGrid extends StatelessWidget { final List> policies; final bool isEnrollment; final Function(String clientPolicyId)? onBulkDownload; static const double _enrollmentCardHeight = 156; static const double _activePolicyCardHeight = 148; const _PolicyGrid({ super.key, required this.policies, required this.isEnrollment, this.onBulkDownload, }); bool _isNonEbPolicy(Map data) { final allocg = (data['allocg'] ?? '').toString().trim().toLowerCase(); return allocg == 'non-eb'; } /// Persist policy context so hrPolicyDetails survives browser refresh. Future _persistHrPolicyContext( TokenStorageService tokenService, { required String clientId, required String policyTypeId, required String clientPolicyId, required String clientBranchId, required String token, required String tokenType, required String cardType, required String cardPolicyNo, required String cardInsurerName, required String cardPolicyName, required String cardPolicyExpDate, required String totalPremium, required dynamic isEcardBulkDownload, }) async { Future write(String key, dynamic value) async { final text = value?.toString() ?? ''; await tokenService.writeValue(key, text); } await write('hr_ClientId', clientId); await write('hr_policyTypeId', policyTypeId); await write('hr_ClientPoliyId', clientPolicyId); await write('hr_clientBranchId', clientBranchId); await write('hr_Token', token); await write('hr_TokenType', tokenType); await write('hr_cardType', cardType); await write('hr_cardPolicyNo', cardPolicyNo); await write('hr_cardInsurer_name', cardInsurerName); await write('hr_cardPolicy_name', cardPolicyName); await write('hr_cardPolicy_ExpDate', cardPolicyExpDate); await write('hr_total_premium', totalPremium); await write( 'hr_is_ecard_bulk_download_for_employee', isEcardBulkDownload ?? 0, ); } ResponsiveGridConfig _getGridConfig( BuildContext context, bool isEnrollment, ) { final width = MediaQuery.of(context).size.width; // if (width < 600) { // return ResponsiveGridConfig(1, isEnrollment ? 1.25 : 1.15); // } else if (width < 900) { // return ResponsiveGridConfig(2, isEnrollment ? 1.6 : 1.45); // } else if (width < 1400) { // return ResponsiveGridConfig(3, isEnrollment ? 3.1 : 2.5); // } else { // return ResponsiveGridConfig(4, isEnrollment ? 3.1 : 2.4); // } if (width >= 1400) { return const ResponsiveGridConfig(4, 2.6); // Big screen } else if (width >= 1000) { return const ResponsiveGridConfig(3, 2.3); // Small desktop } else if (width >= 600) { return const ResponsiveGridConfig(2, 2.0); // Tablet } else { return const ResponsiveGridConfig(1, 1.8); // Mobile } } @override Widget build(BuildContext context) { final tokenService = TokenStorageService(); final config = _getGridConfig(context, isEnrollment); /// βœ… Enrollment β†’ No change if (isEnrollment) { return _buildGrid( context, policies, config, tokenService, ); } /// βœ… Active β†’ Split EB / Non-EB final ebPolicies = policies.where((p) => p['allocg'] == 'EB').toList(); final nonEbPolicies = policies.where((p) => p['allocg'] == 'Non-EB').toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// βœ… EB section if (ebPolicies.isNotEmpty) ...[ Text( 'Employee Benefits', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: const Color(0xFF009195), ), ), const SizedBox(height: 10), _buildGrid( context, ebPolicies, config, tokenService, ), ], /// βœ… Non-EB section if (nonEbPolicies.isNotEmpty) ...[ Text( 'Non-Employee Benefits', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: const Color(0xFFD06A00), ), ), const SizedBox(height: 10), _buildGrid( context, nonEbPolicies, config, tokenService, isNonEb: true, ), ], ], ); } Widget _buildGrid( BuildContext context, List> list, ResponsiveGridConfig config, TokenStorageService tokenService, { bool isNonEb = false, }) { final int rowCount = (list.length / config.crossAxisCount).ceil(); double cardHeight; if (isEnrollment) { cardHeight = _enrollmentCardHeight; } else { cardHeight = _activePolicyCardHeight; } final double totalHeight = rowCount * cardHeight + ((rowCount - 1) * 16); return SizedBox( height: totalHeight, child: GridView.builder( physics: const BouncingScrollPhysics(), shrinkWrap: true, padding: EdgeInsets.zero, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: config.crossAxisCount, childAspectRatio: config.childAspectRatio, crossAxisSpacing: 16, mainAxisSpacing: 16, mainAxisExtent: isEnrollment ? _enrollmentCardHeight : _activePolicyCardHeight, ), itemCount: list.length, itemBuilder: (context, index) { final data = list[index]; return isEnrollment ? _EnrollmentPolicyCardNew( data: data, onTap: () async { final token = await tokenService.getCurrentToken(); final clientId = await tokenService.readValue('enrollmentClient_id'); final branchId = await tokenService .readValue('enrollmentEmpClientBranchId'); if (token == null || clientId == null || branchId == null) { return; } await _persistHrPolicyContext( tokenService, clientId: clientId, policyTypeId: data['policy_type_id'].toString(), clientPolicyId: data['client_policy_id'].toString(), clientBranchId: branchId, token: token, tokenType: 'pre', cardType: data['type'].toString(), cardPolicyNo: data['policy_no'].toString(), cardInsurerName: data['insurer_short_name'].toString(), cardPolicyName: data['policy_name'].toString(), cardPolicyExpDate: data['policy_expiry_date'].toString(), totalPremium: '', isEcardBulkDownload: 0, ); Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: 'hrPolicyDetails'), builder: (_) => hrPolicyDetails( ClientId: clientId, policyTypeId: data['policy_type_id'].toString(), ClientPoliyId: data['client_policy_id'].toString(), clientBranchId: branchId, Token: token, TokenType: "pre", cardType: data['type'].toString(), cardPolicyNo: data['policy_no'].toString(), cardInsurer_name: data['insurer_short_name'].toString(), cardPolicy_name: data['policy_name'].toString(), cardPolicy_ExpDate: data['policy_expiry_date'].toString(), total_premium: '', is_ecard_bulk_download_for_employee: 0, ), ), ); }, ) : _ActivePolicyCardNew( data: data, isNonEb: isNonEb, onBulkDownload: onBulkDownload, onTap: () async { final token = await tokenService.getCurrentToken(); final clientId = await tokenService.readValue('empClientId'); final branchId = await tokenService.readValue('empClientBranchId'); logDebug("token: $token"); logDebug("clientId: $clientId"); logDebug("branchId: $branchId"); if (token == null || clientId == null || branchId == null) { logDebug("Missing required values"); return; } if (_isNonEbPolicy(data)) { Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: 'postFileUpload'), builder: (_) => postFileUpload( ClientId: clientId, policyTypeId: data['policy_type_id'].toString(), ClientPoliyId: data['client_policy_id'].toString(), clientBranchId: branchId, Token: token, TokenType: 'post', cardType: data['type'].toString(), cardPolicyNo: data['policy_no'].toString(), cardInsurer_name: data['insurer_short_name'].toString(), cardPolicy_name: data['policy_name']?.toString() ?? '', cardPolicy_ExpDate: data['policy_expiry_date'].toString(), total_premium: data['total_premium'].toString(), allocgType: 'NONEB', ), ), ); return; } await _persistHrPolicyContext( tokenService, clientId: clientId, policyTypeId: data['policy_type_id'].toString(), clientPolicyId: data['client_policy_id'].toString(), clientBranchId: branchId, token: token, tokenType: 'post', cardType: data['type'].toString(), cardPolicyNo: data['policy_no'].toString(), cardInsurerName: data['insurer_short_name'].toString(), cardPolicyName: data['policy_name'].toString(), cardPolicyExpDate: data['policy_expiry_date'].toString(), totalPremium: data['total_premium'].toString(), isEcardBulkDownload: data['is_ecard_bulk_download_for_employee'], ); Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: 'hrPolicyDetails'), builder: (_) => hrPolicyDetails( ClientId: clientId, policyTypeId: data['policy_type_id'].toString(), ClientPoliyId: data['client_policy_id'].toString(), clientBranchId: branchId, Token: token, TokenType: 'post', cardType: data['type'].toString(), cardPolicyNo: data['policy_no'].toString(), cardInsurer_name: data['insurer_short_name'].toString(), cardPolicy_name: data['policy_name'].toString(), cardPolicy_ExpDate: data['policy_expiry_date'].toString(), total_premium: data['total_premium'].toString(), is_ecard_bulk_download_for_employee: data['is_ecard_bulk_download_for_employee'], ), ), ); }, ); }, ), ); } } // class _PolicyGrid extends StatelessWidget { // final List> policies; // final bool isEnrollment; // final Function(String clientPolicyId)? onBulkDownload; // // const _PolicyGrid({ // super.key, // required this.policies, // required this.isEnrollment, // this.onBulkDownload, // }); // // @override // Widget build(BuildContext context) { // final tokenService = TokenStorageService(); // // return GridView.builder( // physics: const BouncingScrollPhysics(), // βœ… scroll enabled // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // crossAxisCount: 4, // desktop // crossAxisSpacing: 16, // mainAxisSpacing: 16, // childAspectRatio: isEnrollment ? 2.8 : 2.4, // ), // itemCount: policies.length, // itemBuilder: (context, index) { // final data = policies[index]; // // return isEnrollment // ? _EnrollmentPolicyCardNew( // data: data, // onTap: () async { // final String? token = await tokenService.getCurrentToken(); // final String? enrollmentClientId = await tokenService.readValue('enrollmentClient_id'); // final String? enrollmentBranchId = await tokenService.readValue('enrollmentEmpClientBranchId'); // // // βœ… SAFETY CHECK // if (token == null || // enrollmentClientId == null || // enrollmentBranchId == null) { // debugPrint('❌ Missing required data for navigation ${token}'); // return; // } // // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => hrPolicyDetails( // ClientId: enrollmentClientId, // βœ… now String // policyTypeId: data['policy_type_id'].toString(), // ClientPoliyId: data['client_policy_id'].toString(), // clientBranchId: enrollmentBranchId, // βœ… now String // Token: token, // βœ… now String // TokenType: "pre", // cardType: data['type'].toString(), // cardPolicyNo: data['policy_no'].toString(), // cardInsurer_name: data['insurer_short_name'].toString(), // cardPolicy_name: data['policy_name'].toString(), // cardPolicy_ExpDate: data['policy_expiry_date'].toString(), // total_premium: '', // is_ecard_bulk_download_for_employee: 0 // ), // ), // ); // }, // ) // : _ActivePolicyCardNew( // data: data, // onBulkDownload: onBulkDownload, // onTap: () async { // final String? token = await tokenService.getCurrentToken(); // final String? empClientId = await tokenService.readValue('empClientId'); // final String? empBranchId = await tokenService.readValue('empClientBranchId'); // // // βœ… SAFETY CHECK // if (token == null || // empClientId == null || // empBranchId == null) { // debugPrint('❌ Missing required data for navigation ${token}'); // return; // } // // logDebug('$token , $empClientId, $empBranchId'); // logDebug(data); // // return; // // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => hrPolicyDetails( // ClientId: empClientId, // <-- from map // policyTypeId: data['policy_type_id'].toString(), // <-- from map // ClientPoliyId: data['client_policy_id'].toString(), // clientBranchId: empBranchId, // Token: token, // TokenType: 'post', // cardType: data['type'].toString(), // cardPolicyNo: data['policy_no'].toString(), // cardInsurer_name: data['insurer_short_name'].toString(), // cardPolicy_name: data['policy_name'].toString(), // cardPolicy_ExpDate: data['policy_expiry_date'].toString(), // total_premium: data['total_premium'].toString(), // is_ecard_bulk_download_for_employee : data['is_ecard_bulk_download_for_employee'], // ), // ), // ); // }, // ); // }, // ); // } // } class _EnrollmentPolicyCardNew extends StatelessWidget { final Map data; final VoidCallback? onTap; const _EnrollmentPolicyCardNew({ required this.data, this.onTap, }); @override Widget build(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.click, // πŸ‘ˆ pointer on hover child: InkWell( borderRadius: BorderRadius.circular(12), onTap: onTap, // πŸ‘ˆ card click child: Container( decoration: BoxDecoration( color: const Color(0xFFE9F6FB), borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ /// Policy Number Tooltip( message: '${data['type']} - ${data['policy_no']}', waitDuration: const Duration(milliseconds: 300), child: Text( '${data['type']} - ${data['policy_no']}', maxLines: 1, overflow: TextOverflow.ellipsis, softWrap: false, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), // const SizedBox(height: 4), // // /// Insurer // Tooltip( // message: data['insurer_name'] ?? '', // waitDuration: const Duration(milliseconds: 300), // child: Text( // data['insurer_name'] ?? '', // maxLines: 1, // overflow: TextOverflow.ellipsis, // softWrap: false, // style: GoogleFonts.poppins( // fontSize: 11, // color: Colors.grey, // ), // ), // ), const SizedBox(height: 8), /// Closes On Text( 'Closes on: ${data['policy_expiry_date'] ?? ''}', style: GoogleFonts.poppins( fontSize: 12, color: Colors.red, ), ), const SizedBox(height: 8), /// STATUS ROW β€” 2Γ—2 grid so four pills fit without overflow Column( children: [ Row( children: [ Expanded( child: _StatusPillCount( label: 'Draft', value: data['membersCountOfDraft'] ?? 0, color: Colors.orange, compact: true, expand: true, ), ), const SizedBox(width: 6), Expanded( child: _StatusPillCount( label: 'Logged In', value: data['membersCountOfLoggedIn'] ?? 0, color: Colors.purple, compact: true, expand: true, ), ), ], ), const SizedBox(height: 6), Row( children: [ Expanded( child: _StatusPillCount( label: 'Submitted', value: data['membersCountOfEnrolled'] ?? 0, color: Colors.blue, compact: true, expand: true, ), ), const SizedBox(width: 6), Expanded( child: _StatusPillCount( label: 'Total', value: data['totalMembersCount'] ?? 0, color: Colors.green, compact: true, expand: true, ), ), ], ), ], ), ], ), ), ), ); } } String? _formatTpaDisplayName(Map data) { final tpaName = data['tpa_name']?.toString().trim() ?? ''; final tpaShortName = data['tpa_short_name']?.toString().trim() ?? ''; final raw = tpaName.isNotEmpty ? tpaName : tpaShortName; if (raw.isEmpty) return null; final normalized = raw.toLowerCase().replaceAll(RegExp(r'[\s_-]+'), ''); if (normalized == 'inhouse') { return 'In-house'; } return raw; } class _ActivePolicyCardNew extends StatelessWidget { final Map data; final VoidCallback? onTap; final Function(String clientPolicyId)? onBulkDownload; final bool isNonEb; const _ActivePolicyCardNew({ required this.data, this.onTap, this.onBulkDownload, this.isNonEb = false, }); @override Widget build(BuildContext context) { final tpaLabel = _formatTpaDisplayName(data); return MouseRegion( cursor: SystemMouseCursors.click, // πŸ‘ˆ pointer on hover child: InkWell( borderRadius: BorderRadius.circular(12), onTap: onTap, // πŸ‘ˆ card click child: Container( decoration: BoxDecoration( color: isNonEb ? const Color(0xFFFFF3E0) // πŸ”₯ Non-EB : const Color(0xFFE9F6FB), // EB borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// POLICY NO + optional ecard bulk download Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( '${data['type']} - ${data['policy_no']}', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), if (data['is_ecard_bulk_download'] == 1) GestureDetector( onTap: () { logDebug( 'ICON CLICKED ${data['client_policy_id']}'); onBulkDownload?.call( data['client_policy_id'].toString(), ); }, child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: const Color(0xFF009195), borderRadius: BorderRadius.circular(6), ), child: const Icon( Icons.credit_card, color: Colors.white, size: 16, ), ), ), ], ), const SizedBox(height: 2), /// Insurer Tooltip( message: data['insurer_name'] ?? '', waitDuration: const Duration(milliseconds: 300), child: Text( data['insurer_name'] ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, softWrap: false, style: GoogleFonts.poppins( fontSize: 11, color: Colors.grey, ), ), ), if (tpaLabel != null) ...[ const SizedBox(height: 2), Tooltip( message: tpaLabel, waitDuration: const Duration(milliseconds: 300), child: Text( tpaLabel, maxLines: 1, overflow: TextOverflow.ellipsis, softWrap: false, style: GoogleFonts.poppins( fontSize: 11, color: Colors.grey, ), ), ), ], const SizedBox(height: 2), /// DATE RANGE Text( '${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}', style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF8A9B0F), ), ), const SizedBox(height: 8), /// ACTIVE / INACTIVE Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _StatusPillCount( label: 'Active', value: data['membersCountOfActive'] ?? 0, color: Colors.green, ), _StatusPillCount( label: 'Inactive', value: data['membersCountOfInactive'] ?? 0, color: Colors.red, ), ], ), ], ), ))); } } class _StatusPillCount extends StatelessWidget { final String label; final int value; final Color color; final bool compact; final bool expand; const _StatusPillCount({ required this.label, required this.value, required this.color, this.compact = false, this.expand = false, }); @override Widget build(BuildContext context) { return Container( width: expand ? double.infinity : null, height: expand ? 30 : null, padding: EdgeInsets.symmetric( horizontal: compact ? 8 : 18, vertical: compact ? 4 : 5, ), alignment: expand ? Alignment.center : null, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: compact ? 11 : 13, color: color, ), ), ), SizedBox(width: compact ? 4 : 8), Text( value.toString(), style: GoogleFonts.poppins( fontSize: compact ? 12 : 14, fontWeight: FontWeight.bold, color: color, ), ), ], ), ); } } class _ActiveExpiredToggle extends StatelessWidget { final int selectedIndex; final Function(int) onChange; const _ActiveExpiredToggle({ required this.selectedIndex, required this.onChange, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: const Color(0xFFE6F4F3), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ _ToggleItem( label: 'Active', active: selectedIndex == 1, onTap: () => onChange(1), ), _ToggleItem( label: 'Expired', active: selectedIndex == 2, onTap: () => onChange(2), ), ], ), ); } } class _ToggleItem extends StatelessWidget { final String label; final bool active; final VoidCallback onTap; const _ToggleItem({ required this.label, required this.active, required this.onTap, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 6), decoration: BoxDecoration( color: active ? const Color(0xFF009195) : Colors.transparent, borderRadius: BorderRadius.circular(16), ), child: Text( label, style: GoogleFonts.poppins( fontSize: 11, color: active ? Colors.white : Colors.black, fontWeight: FontWeight.w500, ), ), ), ); } } class _EmptyBox extends StatelessWidget { final String text; const _EmptyBox(this.text); @override Widget build(BuildContext context) { return Container( height: 180, width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), border: Border.all(color: Colors.black12), ), child: Center( child: Text(text, style: GoogleFonts.poppins(color: Colors.grey)), ), ); } }