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), Align( alignment: Alignment.bottomRight, child: Text( '* Premium may vary subject to claims', style: GoogleFonts.poppins( fontSize: 10, color: Colors.red), ), ), ], ), ), ] ], ), )), ); } } 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; 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'; } 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( 'EB Policies', 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-EB Policies', 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 = 140; } else { cardHeight = 170; } 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 ? 120 : 150, // fixed card height ), 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; } 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; } 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.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, 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: 10), /// Closes On Text( 'Closes on: ${data['policy_expiry_date'] ?? ''}', style: GoogleFonts.poppins( fontSize: 12, color: Colors.red, ), ), const SizedBox(height: 10), /// STATUS ROW Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _StatusPillCount( label: 'Draft', value: data['membersCountOfDraft'] ?? 0, color: Colors.orange, ), _StatusPillCount( label: 'Under Process', value: data['membersCountOfEnrolled'] ?? 0, color: Colors.blue, ), _StatusPillCount( label: 'Total', value: data['totalMembersCount'] ?? 0, color: Colors.green, ), ], ), ], ), ), ), ); } } 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) { 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(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// PREMIUM Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( 'Premium - β‚Ή${data['total_premium'] ?? ''}*', style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF009195), fontWeight: FontWeight.w600, ), ), ), // πŸ”₯ ICON FLOATING ABOVE CARD if (data['is_ecard_bulk_download'] == 1) Positioned( top: 10, right: 10, child: 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: 4), /// POLICY NO + ICON Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( '${data['type']} - ${data['policy_no']}', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), 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: 4), /// DATE RANGE Text( '${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}', style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF8A9B0F), ), ), const SizedBox(height: 10), /// 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; const _StatusPillCount({ required this.label, required this.value, required this.color, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 5), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( children: [ Text( label, style: GoogleFonts.poppins(fontSize: 13, color: color), ), const SizedBox(width: 8), Text( value.toString(), style: GoogleFonts.poppins( fontSize: 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)), ), ); } }