From 1fb7c324a05be4ee858b45d1cf142c1ca5d26ee9 Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Fri, 13 Feb 2026 09:16:57 +0530 Subject: [PATCH] new design bug issue fix --- lib/branch/branch_card_widget.dart | 79 +- lib/branch/branch_selection_page.dart | 275 +++--- lib/config/environment.dart | 29 +- lib/customAppBar/base_layout.dart | 12 +- lib/customAppBar/side_bar.dart | 24 +- lib/customAppBar/toastHelper.dart | 7 +- lib/email_verify.dart | 42 +- lib/hrLogin.dart | 4 +- lib/main.dart | 2 + lib/presentation/RaiseClaimForm.dart | 52 +- lib/presentation/cdList.dart | 104 ++- lib/presentation/cdTransactionDetails.dart | 813 ++++++++--------- lib/presentation/claims.dart | 794 ++++++----------- lib/presentation/hrDashboard.dart | 658 +++++++------- lib/presentation/hrPolicyDetails.dart | 981 +++++++++++++++------ lib/presentation/policies.dart | 461 ++++++---- lib/presentation/postFileUpload.dart | 7 +- lib/presentation/preFileUpload.dart | 1 + lib/service/_ResponsiveGridConfig.dart | 41 + lib/service/svg_service.dart | 24 +- 20 files changed, 2379 insertions(+), 2031 deletions(-) create mode 100644 lib/service/_ResponsiveGridConfig.dart diff --git a/lib/branch/branch_card_widget.dart b/lib/branch/branch_card_widget.dart index d2fd919..1b3342f 100755 --- a/lib/branch/branch_card_widget.dart +++ b/lib/branch/branch_card_widget.dart @@ -17,47 +17,52 @@ class BranchCard extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(8), - child: Container( - decoration: BoxDecoration( - color: isSelected ? Color(0xFF00999E) : Color(0xFFF0F9F9), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Color(0xFF00999E), - width: 1.5, + return Tooltip( + message: clientName, // πŸ–± hover shows full name + waitDuration: const Duration(milliseconds: 400), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFF00999E) : const Color(0xFFF0F9F9), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFF00999E), + width: 1.5, + ), ), - ), - padding: EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - clientName, - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w500, - color: isSelected ? Colors.white : Color(0xFF00999E), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + clientName, + maxLines: 1, // βœ… SINGLE LINE + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w500, + color: + isSelected ? Colors.white : const Color(0xFF00999E), + ), ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 4), - Text( - branchName, - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: isSelected ? Color(0xFFFDFDFD) : Color(0xFF000000), + const SizedBox(height: 4), + Text( + branchName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 11, + color: + isSelected ? Colors.white70 : Colors.black87, + ), ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + ], + ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/branch/branch_selection_page.dart b/lib/branch/branch_selection_page.dart index 5af22df..e53b58f 100755 --- a/lib/branch/branch_selection_page.dart +++ b/lib/branch/branch_selection_page.dart @@ -1,14 +1,10 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; - import '../customAppBar/base_layout.dart'; -import '../customAppBar/customAppBar.dart'; -import '../customAppBar/customFooter.dart'; +import '../customAppBar/base_layout.dart'; import '../customAppBar/toastHelper.dart'; import '../service/api_service.dart'; -import '../service/session/web_session.dart'; import '../service/token_storage_service.dart'; import 'branch_card_widget.dart'; +import 'dart:html' as html; class BranchSelectionPage extends StatefulWidget { const BranchSelectionPage({Key? key}) : super(key: key); @@ -19,14 +15,30 @@ class BranchSelectionPage extends StatefulWidget { class _BranchSelectionPageState extends State { List> branches = []; - late ApiService apiService; int? selectedIndex; + final tokenStorage = TokenStorageService(); + late ApiService apiService; @override void initState() { super.initState(); apiService = ApiService(context); + html.window.onPopState.listen((event) async { + final shouldLogout = await _showLogoutDialog(); + if (shouldLogout) { + await apiService.logout(); + if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'hrLogin', + (route) => false, + ); + } else { + // Push state back to prevent browser navigation + html.window.history.pushState(null, '', html.window.location.href); + } + }); _loadBranches(); } @@ -37,25 +49,18 @@ class _BranchSelectionPageState extends State { } void _selectBranch(int index) { - setState(() { - selectedIndex = index; - }); + setState(() => selectedIndex = index); } Future _handleNext() async { if (selectedIndex == null) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please select a branch'), - backgroundColor: Colors.orange, - ), + const SnackBar(content: Text('Please select a branch')), ); return; } final selectedBranch = branches[selectedIndex!]; - - // Save selected branch & decode token securely await tokenStorage.saveSelectedBranch(selectedBranch); final decodedToken = tokenStorage.getDecodedToken(); @@ -97,169 +102,121 @@ class _BranchSelectionPageState extends State { false; } - @override Widget build(BuildContext context) { return BaseLayout( child: PopScope( - canPop: false, // 🚫 block default back - onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - if (logout) { - await apiService.logout(); - if (!mounted) return; - Navigator.pushNamedAndRemoveUntil( - context, - 'hrLogin', - (route) => false, - ); - } - }, + canPop: false, child: _buildContent(context), ), ); } - Widget _buildContent(BuildContext context) { - final screenHeight = MediaQuery.of(context).size.height; - final screenWidth = MediaQuery.of(context).size.width; - - // Calculate dynamic horizontal padding (10% of screen width) - final horizontalPadding = screenWidth * 0.1; - - // Calculate dynamic grid height - double gridHeight = screenHeight * 0.45; - if (gridHeight < 300) gridHeight = 300; - if (gridHeight > 600) gridHeight = 600; - - // Determine crossAxisCount with multiple breakpoints - int crossAxisCount; - double childAspectRatio; - - if (screenWidth < 600) { - // Mobile phones - crossAxisCount = 2; - childAspectRatio = 2.5; - } else if (screenWidth < 900) { - // Small tablets - crossAxisCount = 2; - childAspectRatio = 2.8; - } else if (screenWidth < 1200) { - // Large tablets - crossAxisCount = 3; - childAspectRatio = 3; - } else { - // Desktop - crossAxisCount = 3; - childAspectRatio = 4; - } + Widget _buildContent(BuildContext context) { return Scaffold( - backgroundColor: Color(0xFFF5F7F7), - body: Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: horizontalPadding, // Inner horizontal padding - vertical: 20, // Inner vertical padding - ), // Outer padding - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: Offset(0, 4), - ), - ], + backgroundColor: const Color(0xFFF5F7F7), + body: LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth; + + /// πŸ”Ή RESPONSIVE BREAKPOINTS + int crossAxisCount; + if (width < 600) { + crossAxisCount = 1; + } else if (width < 900) { + crossAxisCount = 2; + } else { + crossAxisCount = 3; + } + + return SingleChildScrollView( + padding: EdgeInsets.symmetric( + horizontal: width * 0.08, + vertical: 30, + ), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 12, + offset: const Offset(0, 4), ), - padding: const EdgeInsets.symmetric( - horizontal: 24.0, // Inner horizontal padding - vertical: 28.0, // Inner vertical padding + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Select Client', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Select Client', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - SizedBox(height: 25), - Container( - height: gridHeight, // Shows approximately 3 rows - child: branches.isEmpty - ? Center( - child: Text( - 'No branches available', - style: TextStyle( - fontSize: 16, color: Colors.grey), + const SizedBox(height: 24), + + /// πŸ”Ή GRID + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: branches.length, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisExtent: 90, // πŸ”₯ FIXED HEIGHT + mainAxisSpacing: 15, + crossAxisSpacing: 15, + ), + itemBuilder: (context, index) { + final branch = branches[index]; + return BranchCard( + clientName: branch['client_name'] ?? + 'Unknown Client kjbgjsk jsdhbjbf sdjfjbds fdjsgjdbs gdsjbgjds', + branchName: + branch['branch_name'] ?? 'Unknown Branch', + isSelected: selectedIndex == index, + onTap: () => _selectBranch(index), + ); + }, + ), + + const SizedBox(height: 30), + + /// πŸ”Ή NEXT BUTTON + Center( + child: SizedBox( + width: 140, + height: 46, + child: ElevatedButton( + onPressed: _handleNext, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFFF6B35), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), ), - ) - : GridView.builder( - // Enable scrolling if content exceeds height - physics: ClampingScrollPhysics(), - padding: EdgeInsets.zero, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - childAspectRatio: childAspectRatio, - crossAxisSpacing: 15, - mainAxisSpacing: 15, - ), - itemCount: branches.length, - itemBuilder: (context, index) { - final branch = branches[index]; - return BranchCard( - clientName: branch['client_name'] ?? - 'Unknown Client', - branchName: branch['branch_name'] ?? - 'Unknown Branch', - isSelected: selectedIndex == index, - onTap: () => _selectBranch(index), - ); - }, + elevation: 0, ), - ), - SizedBox(height: 24), - Center( - child: SizedBox( - width: 120, - height: 48, - child: ElevatedButton( - onPressed: _handleNext, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFFF6B35), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - elevation: 0, - ), - child: Text( - 'Next', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), + child: const Text( + 'Next', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, ), ), ), - ], + ), ), - ), + ], ), ), - ), - ], + ); + }, ), ); - } + } } diff --git a/lib/config/environment.dart b/lib/config/environment.dart index 648d6bb..a0fb58b 100755 --- a/lib/config/environment.dart +++ b/lib/config/environment.dart @@ -44,4 +44,31 @@ class Environment { return "/"; } } -} + static String getPageTitle(String? routeName) { + String base = "Nhance HR"; + if (isProd) base = "Nhance HR"; // You can differentiate names if needed + + switch (routeName) { + case 'hrDashboard': + return "$base - Insights"; + case 'policies': + return "$base - Policies"; + case 'CdPoliciesList': + return "$base - CD Details"; + case 'ClaimsPolicies': + return "$base - Claims"; + case 'hrLogin': + return "$base - Login"; + case 'cdTransactionDetails': + return "$base - Transaction Details"; + case 'hrPolicyDetails': + return "$base - Member Details"; + case 'preFileUpload': + return "$base - Member Upload"; + case 'postFileUpload': + return "$base - Member Upload"; + default: + return base; + } + } +} \ No newline at end of file diff --git a/lib/customAppBar/base_layout.dart b/lib/customAppBar/base_layout.dart index 9dae729..6143d21 100644 --- a/lib/customAppBar/base_layout.dart +++ b/lib/customAppBar/base_layout.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'top_app_bar.dart'; import 'side_bar.dart'; +import '../config/environment.dart'; class BaseLayout extends StatelessWidget { final Widget child; @@ -9,7 +10,14 @@ class BaseLayout extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( + // 1. Capture the current route name from the navigation context + final String? routeName = ModalRoute.of(context)?.settings.name; + + // 2. Use the Title widget to communicate with the browser's tab + return Title( + title: Environment.getPageTitle(routeName), + color: const Color(0xFF00999E), // Required, usually matches your theme + child: Scaffold( appBar: const NhanceTopBar(), body: Row( children: [ @@ -23,7 +31,7 @@ class BaseLayout extends StatelessWidget { ), ], ), - ); + ) ); } } diff --git a/lib/customAppBar/side_bar.dart b/lib/customAppBar/side_bar.dart index 3a4d987..73c55ae 100644 --- a/lib/customAppBar/side_bar.dart +++ b/lib/customAppBar/side_bar.dart @@ -23,6 +23,8 @@ class _NhanceSideBarState extends State { // dynamic postModules = []; List> sideMenuItems = []; + List postModules = []; + List enrollmentModules = []; @override @@ -46,12 +48,12 @@ class _NhanceSideBarState extends State { print('postRaw $postRaw'); // βœ… Decode safely - final List enrollmentModules = + enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty ? List.from(jsonDecode(enrollmentRaw)) : []; - final List postModules = + postModules = postRaw != null && postRaw.isNotEmpty ? List.from(jsonDecode(postRaw)) : []; @@ -107,16 +109,20 @@ class _NhanceSideBarState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - activeRoute = ModalRoute.of(context)?.settings.name; + // Safely capture the current route name + final newRoute = ModalRoute.of(context)?.settings.name; + if (activeRoute != newRoute) { + setState(() { + activeRoute = newRoute; + }); + // Re-verify the menu items if the route changes + _buildSideMenu(); + } } void _navigate(String routeName) { if (activeRoute == routeName) return; - - setState(() { - activeRoute = routeName; - }); - + // Navigation should happen first, didChangeDependencies will handle the state Navigator.pushReplacementNamed(context, routeName); } @@ -214,7 +220,7 @@ class _NhanceSideBarState extends State { ); }).toList(), - if(activeRoute == 'CdPoliciesList' || activeRoute == 'ClaimsPolicies' || activeRoute == 'policies' || activeRoute == 'hrDashboard') + if(postModules.isNotEmpty) _SideItem( // icon: Icons.dashboard, icon: SvgPicture.string( diff --git a/lib/customAppBar/toastHelper.dart b/lib/customAppBar/toastHelper.dart index 05e1cea..09accc7 100755 --- a/lib/customAppBar/toastHelper.dart +++ b/lib/customAppBar/toastHelper.dart @@ -116,7 +116,12 @@ class ToastHelper { type: ToastificationType.error, style: ToastificationStyle.flatColored, autoCloseDuration: const Duration(seconds: 2), - title: Text(message), + title: Text( + message, + maxLines: 3, // βœ… allow wrapping + overflow: TextOverflow.visible, + softWrap: true, + ), // you can also use RichText widget for title and description parameters // description: RichText( // text: const TextSpan(text: 'This is a sample toast message. ')), diff --git a/lib/email_verify.dart b/lib/email_verify.dart index 3f469cb..c9e7592 100755 --- a/lib/email_verify.dart +++ b/lib/email_verify.dart @@ -160,12 +160,42 @@ class _MyEmailVerifyState extends State { ); // Navigate to branch selection - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => BranchSelectionPage(), - ), - ); + // πŸ”₯ Get all valid branches + final branches = tokenStorage.getCombinedBranches(); + + if (branches.length == 1) { + // βœ… ONLY ONE BRANCH β†’ AUTO SELECT + final singleBranch = branches.first; + + // Save selected branch + await tokenStorage.saveSelectedBranch(singleBranch); + + final decodedToken = tokenStorage.getDecodedToken(); + final token = tokenStorage.getCurrentToken(); + + if (decodedToken == null || token == null) { + ToastHelper.showErrorToast(context, 'Invalid token data'); + return; + } + + // Save decoded session values + await tokenStorage.saveDecodedSessionData(decodedToken, token); + + if (!mounted) return; + + // πŸš€ DIRECTLY GO TO POLICIES + Navigator.pushReplacementNamed(context, 'policies'); + } else { + // 🧭 MULTIPLE BRANCHES β†’ SHOW SELECTION PAGE + if (!mounted) return; + + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const BranchSelectionPage(), + ), + ); + } // print('data: $data'); // _token = data['data']; // String status = data['status']; diff --git a/lib/hrLogin.dart b/lib/hrLogin.dart index 80bfa97..a0c70af 100755 --- a/lib/hrLogin.dart +++ b/lib/hrLogin.dart @@ -168,7 +168,9 @@ class _MyPhoneState extends State { setState(() { _isLoading = false; }); - ToastHelper.showErrorToast(context, 'Something went wrong'); + Map data = json.decode(response.body); + final message = data['message']; + ToastHelper.showErrorToast(context, message); throw Exception('Failed to verify mobile number'); } } diff --git a/lib/main.dart b/lib/main.dart index 7a928b0..d71f367 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,6 +63,8 @@ Future startApp() async { // await dotenv.load(fileName: Environment.fileName); runApp(MaterialApp( + title: 'Nhance HR', + onGenerateTitle: (context) => "Nhance HR", initialRoute: 'hrLogin', debugShowCheckedModeBanner: false, theme: ThemeData( diff --git a/lib/presentation/RaiseClaimForm.dart b/lib/presentation/RaiseClaimForm.dart index 60c8775..2380ddc 100644 --- a/lib/presentation/RaiseClaimForm.dart +++ b/lib/presentation/RaiseClaimForm.dart @@ -84,6 +84,7 @@ class _RaiseClaimDialogState extends State { int? selectedClientPolicyId; Map? selectedMemberObject; + TextEditingController searchController = TextEditingController(); // Declare subjectController and bodyController as instance variables late TextEditingController subjectController; @@ -1091,23 +1092,50 @@ class _RaiseClaimDialogState extends State { String displayField, int? selectedValue, ) { - final TextEditingController searchController = TextEditingController(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - fieldLabel(label), - formBox( + Text( + label, + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 4), + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Color(0xFFF1F1F1), + borderRadius: BorderRadius.circular(8), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 8, + // offset: const Offset(0, 2), + // ), + // ], + ), child: DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, value: selectedValue, - hint: const Text('Select'), + hint: const Text( + 'Select', + style: TextStyle(fontSize: 12), + ), + iconStyleData: const IconStyleData( icon: Icon(Icons.keyboard_arrow_down), ), - // πŸ”Ή SEARCH CONFIG + dropdownStyleData: DropdownStyleData( + maxHeight: 260, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + ), + + // πŸ” SEARCH SUPPORT dropdownSearchData: DropdownSearchData( searchController: searchController, searchInnerWidgetHeight: 50, @@ -1115,24 +1143,20 @@ class _RaiseClaimDialogState extends State { padding: const EdgeInsets.all(8), child: TextField( controller: searchController, + style: const TextStyle(fontSize: 12), decoration: InputDecoration( hintText: 'Search...', + hintStyle: const TextStyle(fontSize: 12), + isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), - isDense: true, ), ), ), searchMatchFn: (item, searchValue) { - return item.value - .toString() - .toLowerCase() - .contains(searchValue.toLowerCase()) || - item.child - .toString() - .toLowerCase() - .contains(searchValue.toLowerCase()); + final text = item.child.toString().toLowerCase(); + return text.contains(searchValue.toLowerCase()); }, ), diff --git a/lib/presentation/cdList.dart b/lib/presentation/cdList.dart index 2812e42..512a142 100644 --- a/lib/presentation/cdList.dart +++ b/lib/presentation/cdList.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; +import 'package:nhancepolicy/presentation/policies.dart'; import 'package:universal_html/html.dart' as html; import 'cdTransactionDetails.dart'; @@ -57,10 +58,26 @@ class _CdPoliciesListState extends State { return filteredData.sublist(startIndex, endIndex); } + @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here + html.window.onPopState.listen((event) async { + final shouldLogout = await _showLogoutDialog(); + if (shouldLogout) { + await apiService.logout(); + if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'hrLogin', + (route) => false, + ); + } else { + // Push state back to prevent browser navigation + html.window.history.pushState(null, '', html.window.location.href); + } + }); checkIds(); } @@ -233,19 +250,7 @@ class _CdPoliciesListState extends State { Widget build(BuildContext context) { return BaseLayout( child: PopScope( - canPop: false, // 🚫 block default back - onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - if (logout) { - await apiService.logout(); - if (!mounted) return; - Navigator.pushNamedAndRemoveUntil( - context, - 'hrLogin', - (route) => false, - ); - } - }, + canPop: false, child: _buildContent(context), ), ); @@ -267,16 +272,16 @@ class _CdPoliciesListState extends State { /// πŸ”™ Back + Title (LEFT) Row( children: [ - IconButton( - onPressed: () => {}, - icon: const Icon( - Icons.arrow_back_ios, - size: 18, - color: Colors.black, - ), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), + // IconButton( + // onPressed: () => {}, + // icon: const Icon( + // Icons.arrow_back_ios, + // size: 18, + // color: Colors.black, + // ), + // padding: EdgeInsets.zero, + // constraints: const BoxConstraints(), + // ), const SizedBox(width: 6), Text( 'CD', @@ -362,20 +367,42 @@ class _CdPoliciesListState extends State { ); } + + Widget _buildCDGrid() { if (filteredData.isEmpty) { return const Center( child: Text('No CD Account Mapped'), ); } + ResponsiveGridConfig _getGridConfig( + BuildContext context, + bool isEnrollment, + ) { + final width = MediaQuery.of(context).size.width; + + if (width < 600) { + return ResponsiveGridConfig(1, 3.8); + } else if (width < 900) { + return ResponsiveGridConfig(2, 3.8); + } else if (width < 1400) { + return ResponsiveGridConfig(3, 4.2); + } else { + return ResponsiveGridConfig(4, 3.8); + } + } + final config = _getGridConfig(context, true); + return GridView.builder( itemCount: filteredData.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, // desktop + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.zero, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: config.crossAxisCount, + childAspectRatio: config.childAspectRatio, crossAxisSpacing: 16, mainAxisSpacing: 16, - childAspectRatio: 3.8, // πŸ”₯ matches image ), itemBuilder: (context, index) { return InkWell( @@ -385,6 +412,7 @@ class _CdPoliciesListState extends State { Navigator.push( context, MaterialPageRoute( + settings: const RouteSettings(name: 'cdTransactionDetails'), builder: (_) => cdTransactionDetails( insurerName: filteredData[index]['insurer_name'], cdMasterAccountNo: filteredData[index]['cd_master_account_no'], @@ -428,13 +456,13 @@ class _CDPolicyCard extends StatelessWidget { border: Border.all(color: const Color(0xFFA0D1D3)), ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - flex: 7, + flex: 7, child: Text( data['insurer_name'] ?? '', maxLines: 2, @@ -447,7 +475,7 @@ class _CDPolicyCard extends StatelessWidget { ), ), Expanded( - flex: 5, + flex: 3, child: Text( "β‚Ή${balance.toStringAsFixed(0)}", textAlign: TextAlign.right, @@ -466,14 +494,20 @@ class _CDPolicyCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ /// Account number - Text( - 'CD Account Number: ${data['cd_master_account_no']}', - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w400, - color: Color(0xFF000000), + Expanded( + flex: 8, + child: Text( + 'CD No: ${data['cd_master_account_no']}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w400, + color: Color(0xFF000000), + ), ), ), + Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( diff --git a/lib/presentation/cdTransactionDetails.dart b/lib/presentation/cdTransactionDetails.dart index 87b297b..72ffb39 100755 --- a/lib/presentation/cdTransactionDetails.dart +++ b/lib/presentation/cdTransactionDetails.dart @@ -67,6 +67,7 @@ class _cdTransactionDetailsState extends State { dynamic total_consumed; dynamic total_refund; dynamic currect_balance; + dynamic insurer_short_name; int inceptionType = 0; TextEditingController searchController = TextEditingController(); late ApiService apiService; @@ -114,10 +115,11 @@ class _cdTransactionDetailsState extends State { List>.from(response['data']['deposit_data']); originalData = getCDTransData; filteredData = List.from(originalData); - total_deposit = response['data']['total_deposit']; - total_consumed = response['data']['total_consumed']; - total_refund = response['data']['total_refund']; - currect_balance = response['data']['currect_balance']; + total_deposit = formatAmount(response['data']['total_deposit']); + total_consumed = formatAmount(response['data']['total_consumed']); + total_refund = formatAmount(response['data']['total_refund']); + currect_balance = formatAmount(response['data']['currect_balance']); + insurer_short_name = formatAmount(response['data']['insurer_short_name']); print('filteredData'); print(filteredData); }); @@ -401,7 +403,7 @@ class _cdTransactionDetailsState extends State { final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); final anchor = html.AnchorElement(href: url) - ..setAttribute("download", "CD_Policies.csv") + ..setAttribute("download", "CD_Transaction_Policies.csv") ..click(); html.Url.revokeObjectUrl(url); handleExportAction(); @@ -441,7 +443,7 @@ class _cdTransactionDetailsState extends State { try { DateTime parsedDate = DateTime.parse(dateString); - return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); + return DateFormat('dd-MM-yyyy').format(parsedDate); } catch (e) { return '-'; } @@ -495,22 +497,22 @@ class _cdTransactionDetailsState extends State { false; } + String formatAmount(dynamic value) { + if (value == null) return '-'; + + final num amount = num.tryParse(value.toString()) ?? 0; + return amount.round().toString(); + } + + @override Widget build(BuildContext context) { return BaseLayout( child: PopScope( canPop: false, // 🚫 block default back onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - if (logout) { - await apiService.logout(); - if (!mounted) return; - Navigator.pushNamedAndRemoveUntil( - context, - 'hrLogin', - (route) => false, - ); - } + if (didPop) return; + Navigator.pop(context); // πŸ‘ˆ go to previous page }, child: _buildContent(context), ), @@ -519,7 +521,7 @@ class _cdTransactionDetailsState extends State { Widget _buildContent(BuildContext context) { return isLoading ? Container( - color: Color(0x98FFFCE5), // Semi-transparent background + color: Colors.transparent, // Semi-transparent background child: Center( child: // Your GIF loader widget Image.asset( @@ -538,6 +540,7 @@ class _cdTransactionDetailsState extends State { Row( children: [ IconButton( + tooltip: 'Previous Page', onPressed: () => Navigator.pop(context), icon: const Icon( Icons.arrow_back_ios, @@ -549,7 +552,7 @@ class _cdTransactionDetailsState extends State { ), const SizedBox(width: 6), Text( - 'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})', + 'Transaction Details - ${insurer_short_name} (${widget.cdMasterAccountNo})', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w500, @@ -627,30 +630,12 @@ class _cdTransactionDetailsState extends State { ], ), SizedBox(height: 16), - Container( - decoration: BoxDecoration( - // color: Colors.white, - borderRadius: BorderRadius.circular( - 10), // πŸ‘ˆ set your desired radius - ), - // height: 400, - // padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: SingleChildScrollView( - child: _buildCDDataTable(context), - ), - ) - ], - ), - ], - ), - ) + Expanded( + child: _buildCDDataTable(context), + ), ], - )); + ) + ); } @@ -711,439 +696,348 @@ class _cdTransactionDetailsState extends State { Widget _buildCDDataTable(BuildContext context) { if (filteredData.isEmpty) { - return const SizedBox( - height: 50, - child: Center(child: Text('No available data')), - ); + return const Center(child: Text('No available data')); } - return 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: [ - Expanded( - flex: 2, - child: Text( - 'Date', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Record Date', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 4, - child: Text( - 'Unit', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 3, - child: Text( - 'Policy', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 3, - child: Text( - 'Endorsement No', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Sub Type', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Credit', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Debit', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Balance', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 3, - child: Text( - 'Description', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'User', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000), - fontWeight: FontWeight.bold), - ), - ), - Expanded( - flex: 2, - child: Text( - 'Action', - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 12, - color: const Color(0xFF000000), - fontWeight: FontWeight.bold, - ), - ), - ), + return CustomScrollView( + slivers: [ + /// πŸ”’ FIXED HEADER + SliverPersistentHeader( + pinned: true, + delegate: _CDTableHeaderDelegate(), + ), - ], + /// πŸ“„ TABLE ROWS + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final item = _paginatedData[index]; + return _buildCDRow(item); + }, + childCount: _paginatedData.length, ), ), - const SizedBox(height: 6), - - // Table body rows - ..._paginatedData.mapIndexed((index, item) { - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16), - decoration: BoxDecoration( - // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white, - // color: Colors.white, - // borderRadius: BorderRadius.circular(6), - border: Border( - bottom: BorderSide( - color: Color(0xFFA9D9DE), // πŸ‘ˆ Bottom border color - width: 1, // πŸ‘ˆ Optional: thickness - ), - ), - ), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text( - formatDateNextLine(item['created_at']), - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - item['record_date'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 4, - child: Text( - // 'The Kancheepuram District Consumers Operative Wholesale Stores Limited-5526', - item['unit'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 3, - child: Text( - // 'Motor - 3001/379707905/00/000', - item['policy_type'] != null && - item['policy_type'].toString().trim().isNotEmpty && - item['policy_no'] != null && - item['policy_no'].toString().trim().isNotEmpty - // ? '${item['policy_no']}' - ? '${item['policy_type']} - ${item['policy_no']}' - : '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 3, - child: Text( - item['endorsement_no'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - item['sub_type_text'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - item['transaction_type'] == 'Credit' - ? 'β‚Ή${item['amount']}' - : '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - item['transaction_type'] == 'Debit' - ? 'β‚Ή${item['amount']}' - : '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - "β‚Ή${item['balance'] ?? '-'}", - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 3, - child: Text( - item['description'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Text( - item['username'] ?? '-', - textAlign: TextAlign.left, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontSize: 12, - ), - ), - ), - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _ActionIconButton( - icon: Icons.picture_as_pdf_outlined, - onTap: () { - getCdEndorsementDetails(item['id']); - }, - subType: item['sub_type'] - ), - const SizedBox(width: 8), - _ActionIconButton( - icon: Icons.folder_open_outlined, - onTap: () { - if (item['split_up_url'] != null && item['split_up_url'].toString().trim().isNotEmpty){ - _launchURL(item['split_up_url']); - } else{ - ToastHelper.showWarningToast(context, 'File not generated'); - } - - }, - subType: item['sub_type'] - ), - ], - ), - ), - - ], - ), - ); - }).toList(), - - 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; - }); - }, - ), - 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), - ), - ], - ), - ), - ], + /// πŸ“Œ PAGINATION + SliverToBoxAdapter( + child: _buildPagination(context), ), ], ); } + + Widget _buildCDRow(Map item) { + final bool isAllowedSubType = + item['sub_type'] == '3' || item['sub_type'] == '4'; + + final bool hasSplitUpFile = + item['split_up_url'] != null && + item['split_up_url'].toString().trim().isNotEmpty; + + return Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFA9D9DE), width: 1), + ), + ), + child: Row( + children: [ + _cell(formatDateNextLine(item['created_at']), 2), + SizedBox(width: 10), + _cell(formatDate(item['record_date']), 2), + SizedBox(width: 10), + _cell(item['unit'], 2), + SizedBox(width: 10), + _cell( + item['policy_type'] != null && item['policy_no'] != null + ? '${item['policy_type']} - ${item['policy_no']}' + : '-', + 3, + ), + SizedBox(width: 10), + _cell(item['endorsement_no'], 3), + SizedBox(width: 10), + _cell(item['sub_type_text'], 2), + SizedBox(width: 10), + _cell( + item['transaction_type'] == 'Credit' + ? formatAmount(item['amount']) + : '-', + 2, + alignRight: true, + ), + SizedBox(width: 10), + _cell( + item['transaction_type'] == 'Debit' + ? formatAmount(item['amount']) + : '-', + 2, + alignRight: true, + ), + SizedBox(width: 10), + _cell(formatAmount(item['balance']), 2, alignRight: true), + SizedBox(width: 10), + _cell(item['description'], 3), + SizedBox(width: 10), + _cell(item['username'], 2), + SizedBox(width: 10), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isAllowedSubType) + _ActionIconButton( + icon: Icons.picture_as_pdf_outlined, + onTap: () => getCdEndorsementDetails(item['id']), + ), + const SizedBox(width: 8), + if (isAllowedSubType && hasSplitUpFile) + _ActionIconButton( + icon: Icons.folder_open_outlined, + onTap: () => _launchURL(item['split_up_url']), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _cell(String? text, int flex, {bool alignRight = false}) { + return Expanded( + flex: flex, + child: Text( + text ?? '-', + textAlign: alignRight ? TextAlign.right : TextAlign.left, + style: GoogleFonts.poppins(fontSize: 12), + // overflow: TextOverflow.ellipsis, + ), + ); + } + + Widget _buildPagination(BuildContext context) { + 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]; + } else if (_currentPage >= totalPages - 2) { + return [ + totalPages - 4, + totalPages - 3, + totalPages - 2, + totalPages - 1, + totalPages + ]; + } else { + return [ + _currentPage - 2, + _currentPage - 1, + _currentPage, + _currentPage + 1, + _currentPage + 2, + ]; + } + } + + List visiblePages = getVisiblePages(); + + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + children: [ + // Dropdown for rows per page + 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; + }); + }, + ), + + // Previous button + IconButton( + 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)) + 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()), + ), + ); + } + } + +class _CDTableHeaderDelegate extends SliverPersistentHeaderDelegate { + @override + double get minExtent => 52; + + @override + double get maxExtent => 52; + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container( + decoration: BoxDecoration( + color: Color(0xFFD7E9EB), + borderRadius: BorderRadius.circular(6), + ), + // color: Color(0xFFD7E9EB), + padding: EdgeInsets.symmetric(horizontal: 16), + alignment: Alignment.centerLeft, + child: const Row( + children: [ + _HeaderCell('Date', 2), + SizedBox(width: 10), + _HeaderCell('Record Date', 2), + SizedBox(width: 10), + _HeaderCell('Unit', 2), + SizedBox(width: 10), + _HeaderCell('Policy', 3), + SizedBox(width: 10), + _HeaderCell('Endorsement No', 3), + SizedBox(width: 10), + _HeaderCell('Sub Type', 2), + SizedBox(width: 10), + _HeaderCell('β‚Ή Credit', 2, alignRight: true), + SizedBox(width: 10), + _HeaderCell('β‚Ή Debit', 2, alignRight: true), + SizedBox(width: 10), + _HeaderCell('β‚Ή Balance', 2, alignRight: true), + SizedBox(width: 10), + _HeaderCell('Description', 3), + SizedBox(width: 10), + _HeaderCell('User', 2), + SizedBox(width: 10), + _HeaderCell('Action', 2, center: true), + SizedBox(width: 10), + ], + ), + ); + } + + @override + bool shouldRebuild(_) => false; +} + +class _HeaderCell extends StatelessWidget { + final String text; + final int flex; + final bool alignRight; + final bool center; + + const _HeaderCell( + this.text, + this.flex, { + this.alignRight = false, + this.center = false, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + flex: flex, + child: Text( + text, + textAlign: + center ? TextAlign.center : (alignRight ? TextAlign.right : TextAlign.left), + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF000000), + ), + ), + ); + } +} + + class _ActionIconButton extends StatelessWidget { final IconData icon; final VoidCallback onTap; - final String? subType; - + final bool enabled; const _ActionIconButton({ required this.icon, required this.onTap, - required this.subType, + this.enabled = true, }); @override @@ -1152,15 +1046,17 @@ class _ActionIconButton extends StatelessWidget { width: 36, height: 36, child: Material( - color: const Color(0xFFDFF4F5), // light teal bg + color: enabled + ? const Color(0xFFDFF4F5) + : Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( borderRadius: BorderRadius.circular(10), - onTap: (subType == '3' || subType == '4') ? onTap : null, + onTap: enabled ? onTap : null, child: Icon( icon, size: 22, - color: Colors.black, + color: enabled ? Colors.black : Colors.transparent, ), ), ), @@ -1169,6 +1065,7 @@ class _ActionIconButton extends StatelessWidget { } + // Sample Data class representing each element in the array class Data { final dynamic value; diff --git a/lib/presentation/claims.dart b/lib/presentation/claims.dart index 00999f6..d7b6fd8 100755 --- a/lib/presentation/claims.dart +++ b/lib/presentation/claims.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:csv/csv.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; @@ -35,6 +36,7 @@ class _ClaimsPolicieState extends State { dynamic empHrId; String? _postPreToken = ''; TextEditingController searchController = TextEditingController(); + TextEditingController searchClaimsStatusController = TextEditingController(); Map controllers = {}; List reversedDataPolicy = []; @@ -102,6 +104,22 @@ class _ClaimsPolicieState extends State { super.initState(); apiService = ApiService(context); + html.window.onPopState.listen((event) async { + final shouldLogout = await _showLogoutDialog(); + if (shouldLogout) { + await apiService.logout(); + if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'hrLogin', + (route) => false, + ); + } else { + // Push state back to prevent browser navigation + html.window.history.pushState(null, '', html.window.location.href); + } + }); + for (String field in tabHeader) { controllers[field] = TextEditingController(); } @@ -147,6 +165,7 @@ class _ClaimsPolicieState extends State { getClaimsPoliciesDetails(); getClaimList(); } + @override void dispose() { for (var controller in controllers.values) { @@ -282,13 +301,14 @@ class _ClaimsPolicieState extends State { List> rows = []; // Header - rows.add(['Emp Name', 'Emp Code', 'Policy Type','Client Policy No','claim_no','status','claim_amount','ticket_created_date']); + rows.add(['Emp Name', 'Emp Code','Insured Name', 'Policy Type','Client Policy No','Claim Number','Claim Type','Status','Claim Amount','Record Date']); // Data rows for (var item in data) { rows.add([ item['emp_name'] ?? '', item['emp_code'] ?? '', + item['insured_name'] ?? '', item['policy_type'] ?? '', item['client_policy_no'] ?? '', item['claim_no'] ?? '', @@ -363,6 +383,10 @@ class _ClaimsPolicieState extends State { .toString() .toLowerCase() .contains(query.toLowerCase()) || + row['insured_name'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || row['policy_type'] .toString() .toLowerCase() @@ -419,23 +443,25 @@ class _ClaimsPolicieState extends State { false; } + // βœ… PUT IT HERE (inside State, outside build) + List> get claimStatusList { + final list = getClaimPoliciesApi['claim_status'] ?? []; + + if (list is! List) return []; + + return list + .map>((e) => { + 'id': int.parse(e['id'].toString()), + 'name': e['claim_status'].toString(), + }) + .toList(); + } + @override Widget build(BuildContext context) { return BaseLayout( child: PopScope( - canPop: false, // 🚫 block default back - onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - if (logout) { - await apiService.logout(); - if (!mounted) return; - Navigator.pushNamedAndRemoveUntil( - context, - 'hrLogin', - (route) => false, - ); - } - }, + canPop: false, child: _buildContent(context), ), ); @@ -457,16 +483,16 @@ class _ClaimsPolicieState extends State { /// πŸ”™ Back + Title (LEFT) Row( children: [ - IconButton( - onPressed: () => {}, - icon: const Icon( - Icons.arrow_back_ios, - size: 18, - color: Colors.black, - ), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), + // IconButton( + // onPressed: () => {}, + // icon: const Icon( + // Icons.arrow_back_ios, + // size: 18, + // color: Colors.black, + // ), + // padding: EdgeInsets.zero, + // constraints: const BoxConstraints(), + // ), const SizedBox(width: 6), Text( 'Claims', @@ -588,7 +614,7 @@ class _ClaimsPolicieState extends State { ), ) : Expanded( - child: _buildClaimsDataTable(context), + child: _buildClaimsDataTable(context), // βœ… THIS IS REQUIRED ), // Expanded( // child: Container( @@ -631,360 +657,62 @@ class _ClaimsPolicieState extends State { ); } + Widget _buildClaimStatus(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + child: buildDropdownFieldSearch( + 'Claim Status', + (int? value) { + setState(() { + selectedClaimStatus = value; + }); + print('Selected Claim Status ID: $selectedClaimStatus'); + }, + claimStatusList, + 'name', + selectedClaimStatus, + ), + ); + } + + Widget _buildClaimsDataTable(BuildContext context) { if (filteredData.isEmpty) { - return const SizedBox( - height: 50, - child: Center(child: Text('No available data')), - ); + return const Center(child: Text('No available data')); } - return ListView.builder( - itemCount: _paginatedData.length + 2, // +1 for header, +1 for pagination - itemBuilder: (context, index) { - if (index == 0) return _buildHeader(); - if (index == _paginatedData.length + 1) - return _buildPagination(context); + return CustomScrollView( + slivers: [ + /// πŸ”’ FIXED HEADER + SliverPersistentHeader( + pinned: true, + delegate: _ClaimsHeaderDelegate(), + ), - final item = _paginatedData[index - 1]; - return _buildDataRow(item); - }, - ); + /// πŸ“„ TABLE ROWS + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final item = _paginatedData[index]; + return _buildDataRow(item); + }, + childCount: _paginatedData.length, + ), + ), - // return Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // // Header row - // Container( - // decoration: BoxDecoration( - // color: Color(0xFF00A6A6), - // borderRadius: BorderRadius.circular(6), - // ), - // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - // child: Row( - // children: [ - // Expanded( - // flex: 4, - // child: Text( - // 'Name', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 4, - // child: Text( - // 'Policy Name', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // 'Claim Number', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 2, - // child: Text( - // 'Status', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // 'Claim Amount', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // 'Record Date', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 2, - // child: Text( - // 'Action', - // textAlign: TextAlign.center, - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // ], - // ), - // ), - // - // const SizedBox(height: 6), - // - // SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: Column( - // 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(0xFFD7E9EB), - // width: 1, - // ), - // ), - // // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white, - // borderRadius: BorderRadius.circular(6), - // ), - // child: Row( - // children: [ - // Expanded( - // flex: 4, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // item['emp_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), - // ), - // ], - // ), - // ), - // Expanded( - // flex: 4, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // item['emp_name'] ?? '-', - // style: GoogleFonts.poppins( - // color: Color(0xFF000000), - // fontWeight: FontWeight.w400, - // fontSize: 12), - // ), - // Text( - // item['policy_no'] ?? '-', - // style: GoogleFonts.poppins( - // color: Color(0xFF585757), - // fontWeight: FontWeight.w300, - // fontSize: 10), - // ), - // ], - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // "${item['claim_no'] ?? ''}", - // 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: Color(0xFF7BD9B6), - // // color: (item['emp_is_active'] == "1") - // // ? Color(0xFF7BD9B6) - // // : Color(0xFFFFA6A6), - // borderRadius: BorderRadius.circular(6), - // ), - // child: Align( - // alignment: Alignment.center, - // child: Text( - // "${item['status'] ?? ''}", - // style: GoogleFonts.poppins( - // color: Color(0xFF000000), - // fontWeight: FontWeight.w500, - // fontSize: 12, - // ), - // ), - // ), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // "${item['claim_amount'] ?? ''}", - // style: GoogleFonts.poppins( - // color: Color(0xFF000000), - // fontWeight: FontWeight.w400, - // fontSize: 12), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // "${item['ticket_created_date'] ?? ''}", - // style: GoogleFonts.poppins( - // color: Color(0xFF000000), - // fontWeight: FontWeight.w400, - // fontSize: 12), - // ), - // ), - // Expanded( - // flex: 2, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // GestureDetector( - // onTap: () {}, - // child: Container( - // height: 30, - // width: 30, - // decoration: BoxDecoration( - // color: Color(0xFFE6F5F6), - // borderRadius: BorderRadius.circular(12)), - // child: Icon(Icons.credit_card, - // color: Color(0xFF3D3D3D)), - // // child: Image.asset( - // // 'assets/ecard.jpg', - // // height: 20, - // // fit: BoxFit.cover, - // // ), - // ), - // ), - // ], - // ), - // ), - // ], - // ), - // ); - // }).toList(), - // ), - // ), - // - // 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 _buildHeader() { - return Container( - decoration: BoxDecoration( - color: Color(0xFF00A6A6), - borderRadius: BorderRadius.circular(6), - ), - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: const Row( - children: [ - Expanded(flex: 4, child: Text('Name', style: _headerStyle)), - Expanded(flex: 4, child: Text('Policy Name', style: _headerStyle)), - Expanded(flex: 3, child: Text('Claim Number', style: _headerStyle)), - Expanded(flex: 4, child: Text('Status', style: _headerStyle)), - Expanded(flex: 3, child: Text('Claim Amount', style: _headerStyle)), - Expanded(flex: 3, child: Text('Record Date', style: _headerStyle)), - Expanded( - flex: 2, - child: Text('Action', - textAlign: TextAlign.center, style: _headerStyle)), - ], - ), + /// πŸ“Œ PAGINATION + SliverToBoxAdapter( + child: _buildPagination(context), + ), + ], ); } + + + + + Widget _buildDataRow(Map item) { return Container( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), @@ -1008,6 +736,15 @@ class _ClaimsPolicieState extends State { ], ), ), + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(item['insured_name'] ?? '-', style: _dataBold), + ], + ), + ), Expanded( flex: 4, child: Column( @@ -1556,7 +1293,7 @@ class _ClaimsPolicieState extends State { context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), - lastDate: DateTime(2100), + lastDate: DateTime.now(), ); if (pickedDate != null) { @@ -1566,6 +1303,7 @@ class _ClaimsPolicieState extends State { print("FomatedFromDAta - $formattedDate"); setState(() { controllers['from']?.text = formattedDate; + controllers['to']?.clear(); // πŸ”₯ prevent invalid To date }); } }, @@ -1633,27 +1371,30 @@ class _ClaimsPolicieState extends State { child: TextField( controller: controllers['to'], readOnly: true, - onTap: () async { - FocusScope.of(context) - .requestFocus(FocusNode()); // hide keyboard - DateTime? pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime(2000), - lastDate: DateTime(2100), - ); + onTap: () async { + FocusScope.of(context).requestFocus(FocusNode()); - if (pickedDate != null) { - String formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}"; + final fromDate = _parseDate(controllers['from']?.text); - print("FomatedFromDAta - $formattedDate"); - setState(() { - controllers['to']?.text = formattedDate; - }); - } - }, - style: const TextStyle( + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: fromDate ?? DateTime.now(), + firstDate: fromDate ?? DateTime(2000), // βœ… cannot be before From date + lastDate: DateTime.now(), // βœ… no future dates + ); + + if (pickedDate != null) { + String formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}-" + "${pickedDate.month.toString().padLeft(2, '0')}-" + "${pickedDate.year}"; + + setState(() { + controllers['to']?.text = formattedDate; + }); + } + }, + style: const TextStyle( fontSize: 12, ), decoration: InputDecoration( @@ -1688,138 +1429,119 @@ class _ClaimsPolicieState extends State { ); } - Widget _buildClaimStatus(BuildContext context) { - List claimStatusList = getClaimPoliciesApi['claim_status'] ?? []; + Widget buildDropdownFieldSearch( + String label, + void Function(int?) onChanged, + List> itemsList, + String displayField, + int? selectedValue, + ) { - // Get type_name from ticket_type id - String? getTypeNameById(int? id) { - if (id == null) return null; - final match = claimStatusList.firstWhere( - (e) => e['id'] == id, - orElse: () => null, - ); - return match != null ? match['claim_status'] : null; - } - - // Get id from type_name - int? getIdByTypeName(String? typeName) { - print("getIdByTypeName"); - print("getIdByTypeName - $typeName"); - final match = claimStatusList.firstWhere( - (e) => e['claim_status'] == typeName, - orElse: () => null, - ); - - print("match - $match"); - - if (match != null && match is Map) { - final map = match as Map; - final id = map['id']; - print("Returning id: $id"); - return id is int ? id : int.tryParse(id.toString()); - } - - print("no match or invalid map"); - - return null; - } - - return Container( - width: MediaQuery.of(context).size.width * 0.15, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Claim Status", - style: TextStyle(fontSize: 12), - ), - const SizedBox( - height: 3, - ), - SizedBox( - height: 40, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.08), // soft shadow - blurRadius: 8, - offset: const Offset(0, 2), // downward shadow - ), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 4), + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + value: selectedValue, + hint: const Text( + 'Select', + style: TextStyle(fontSize: 12), ), - child: DropdownSearch( - selectedItem: selectedClaimStatusName, - items: (String? filter, _) { - return claimStatusList - .map((item) => item['claim_status'].toString()) - .toList(); - }, - onChanged: (value) { - setState(() { - selectedClaimStatusName = value; - selectedClaimStatus = getIdByTypeName(value); - }); - print( - "Selected selectedClaimStatusName: $selectedClaimStatusName"); - print("Selected selectedClaimStatus: $selectedClaimStatus"); + iconStyleData: const IconStyleData( + icon: Icon(Icons.keyboard_arrow_down), + ), + + dropdownStyleData: DropdownStyleData( + maxHeight: 260, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + ), + + // πŸ” SEARCH SUPPORT + dropdownSearchData: DropdownSearchData( + searchController: searchClaimsStatusController, + searchInnerWidgetHeight: 50, + searchInnerWidget: Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: searchClaimsStatusController, + style: const TextStyle(fontSize: 12), + decoration: InputDecoration( + hintText: 'Search...', + hintStyle: const TextStyle(fontSize: 12), + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + searchMatchFn: (item, searchValue) { + final text = item.child.toString().toLowerCase(); + return text.contains(searchValue.toLowerCase()); + }, + ), + + onMenuStateChange: (isOpen) { + if (!isOpen) { + searchClaimsStatusController.clear(); + } }, - dropdownBuilder: (context, selectedItem) { - return Text( - selectedItem ?? "", - style: TextStyle(fontSize: 12), + + items: itemsList.map((item) { + return DropdownMenuItem( + value: item['id'], + child: Text( + item[displayField], + style: const TextStyle(fontSize: 12), + overflow: TextOverflow.ellipsis, + ), ); - }, - decoratorProps: DropDownDecoratorProps( - decoration: InputDecoration( - // suffixStyle: TextStyle(color: Colors.red), - hintText: "Select Policy Type", - hintStyle: TextStyle(fontSize: 12), - filled: true, - fillColor: Colors.white, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.teal, width: 2), - ), - contentPadding: - EdgeInsets.symmetric(horizontal: 12, vertical: 10), - labelStyle: TextStyle(fontSize: 19, color: Colors.red), - ), - ), - popupProps: const PopupProps.menu( - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - showSearchBox: true, - searchFieldProps: TextFieldProps( - style: TextStyle(fontSize: 14.0, color: Colors.red), - decoration: InputDecoration( - labelStyle: TextStyle(fontSize: 19, color: Colors.red), - hintText: "Search Claim Status", - hintStyle: TextStyle(fontSize: 12), - contentPadding: - EdgeInsets.symmetric(horizontal: 12, vertical: 10), - ), - ), - ), + }).toList(), + + onChanged: onChanged, ), - ) ), - ], - ), + ), + ], ); } + DateTime? _parseDate(String? value) { + if (value == null || value.isEmpty) return null; + final parts = value.split('-'); + return DateTime( + int.parse(parts[2]), + int.parse(parts[1]), + int.parse(parts[0]), + ); + } + + + + Widget _buildPolicyNumber(BuildContext context) { return Container( width: MediaQuery.of(context).size.width * 0.15, @@ -1920,6 +1642,54 @@ class _ClaimsPolicieState extends State { } +class _ClaimsHeaderDelegate extends SliverPersistentHeaderDelegate { + @override + double get minExtent => 52; + + @override + double get maxExtent => 52; + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container( + // color: const Color(0xFF00A6A6), + decoration: BoxDecoration( + color: Color(0xFF00A6A6), + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + alignment: Alignment.centerLeft, + child: const Row( + children: [ + Expanded(flex: 4, child: Text('Name', style: _headerStyle)), + Expanded(flex: 3, child: Text('Insured Name', style: _headerStyle)), + Expanded(flex: 4, child: Text('Policy Name', style: _headerStyle)), + Expanded(flex: 3, child: Text('Claim Number', style: _headerStyle)), + Expanded(flex: 4, child: Text('Status', style: _headerStyle)), + Expanded(flex: 3, child: Text('Claim Amount', style: _headerStyle)), + Expanded(flex: 3, child: Text('Record Date', style: _headerStyle)), + Expanded( + flex: 2, + child: Text('Action', + textAlign: TextAlign.center, style: _headerStyle), + ), + ], + ), + ); + } + + static const _headerStyle = TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ); + + @override + bool shouldRebuild(_) => false; +} + + + // ================= ICON BUTTON ================= class _IconActionButton extends StatelessWidget { final IconData icon; diff --git a/lib/presentation/hrDashboard.dart b/lib/presentation/hrDashboard.dart index 7055b64..3436a49 100755 --- a/lib/presentation/hrDashboard.dart +++ b/lib/presentation/hrDashboard.dart @@ -1,68 +1,65 @@ import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/service/api_service.dart'; import 'package:nhancepolicy/service/token_storage_service.dart'; -import 'dart:ui_web' as ui; - +import 'dart:ui_web' as ui; // Standard for Flutter 3.12+ import 'package:universal_html/html.dart' as html; import '../config/environment.dart'; import '../customAppBar/base_layout.dart'; - - class hrDashboard extends StatefulWidget { - hrDashboard({Key? key,}) : super(key: key); + hrDashboard({Key? key}) : super(key: key); @override State createState() => _hrDashboardState(); } -class _hrDashboardState extends State - with SingleTickerProviderStateMixin { +class _hrDashboardState extends State with SingleTickerProviderStateMixin { late ApiService apiService; bool isLoading = false; String? selectedPolicyId; List> activePoliciesList = []; + final Set _registeredViewTypes = {}; - String? _metabaseToken; - String? _metabaseUrl; - bool isPolicyLoading = false; bool isDashboardLoading = false; bool hasDashboardError = false; - - bool _metabaseLoaded = false; // βœ… FIX + bool _metabaseLoaded = false; List postModules = []; - dynamic policy_name; - dynamic getPreCardArrays = []; - dynamic getPostCardArrays = []; - String? _postPreToken = ''; + String _dashboardViewType = ''; + + final tokenService = TokenStorageService(); + final FocusNode _policyFocusNode = FocusNode(); + + // Variables for API data dynamic empClientBranchId; dynamic empHrId; dynamic empClientId; + String? _postPreToken = ''; int stausVal = 1; - String _dashboardViewType = ''; - bool isDropdownOpen = false; - final FocusNode _policyFocusNode = FocusNode(); - - - - final tokenService = TokenStorageService(); + bool _isPolicyDropdownOpen = false; @override void initState() { super.initState(); apiService = ApiService(context); - - _policyFocusNode.addListener(() { - if (!_policyFocusNode.hasFocus) { - setState(() => isDropdownOpen = false); + html.window.onPopState.listen((event) async { + final shouldLogout = await _showLogoutDialog(); + if (shouldLogout) { + await apiService.logout(); + if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'hrLogin', + (route) => false, + ); + } else { + // Push state back to prevent browser navigation + html.window.history.pushState(null, '', html.window.location.href); } }); - _loadToken(); } @@ -80,13 +77,8 @@ class _hrDashboardState extends State ? List.from(jsonDecode(postRaw)) : []; - if (!(postModules.contains(2) || - postModules.contains(3) || - postModules.contains(4))) { - // ❌ No dashboard permission - setState(() { - hasDashboardError = true; - }); + if (!(postModules.contains(2) || postModules.contains(3) || postModules.contains(4))) { + setState(() => hasDashboardError = true); return; } @@ -94,103 +86,38 @@ class _hrDashboardState extends State empClientBranchId = await tokenService.readValue('empClientBranchId'); empHrId = await tokenService.readValue('empHrId'); - await getPostCashDepositDetails( - empClientBranchId, empClientId, empHrId, _postPreToken); + await getPostCashDepositDetails(empClientBranchId, empClientId, empHrId, _postPreToken); } - - Future getPostCashDepositDetails( - empClientBranchId, empClientId, empHrId, _postPreToken) async { - print('IN'); - print("clintBranchId -$empClientBranchId"); - print("clintID -$empClientId"); - print("hr_id -$empHrId"); - print("token -$_postPreToken"); - - isLoading = true; - // setState(() { - // _isLoading = true; - // }); + Future getPostCashDepositDetails(branchId, clientId, hrId, token) async { + setState(() => isLoading = true); try { - if (empClientBranchId == null || empClientId == null) { - return; - } + if (branchId == null || clientId == null) return; final response = await apiService.getActiveCashDepositDetailsToApi( - empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal); + clientId, branchId, hrId, token, stausVal); - // final response = await apiService.getCashDepositDetailsToApi( - // clintID!, clintBranchId!, hr_id, token); - print('IN1'); if (response['status'] == 'success') { final list = List>.from(response['data']); + final List> filteredList = list.where((item) { + final int policyTypeId = int.tryParse(item['policy_type_id'].toString()) ?? 0; + return policyTypeId == 2 || + policyTypeId == 3 || + policyTypeId == 4 || + policyTypeId == 5; + }).toList(); + setState(() => activePoliciesList = filteredList); - setState(() { - activePoliciesList = list; - }); - - // βœ… AUTO SELECT FIRST POLICY HERE if (list.isNotEmpty) { selectedPolicyId = list.first['client_policy_id'].toString(); await _loadDashboardByPolicy(selectedPolicyId!); } else { - setState(() { - hasDashboardError = true; - }); + setState(() => hasDashboardError = true); } - } else { - isLoading = false; - print('API request failed with status'); - setState(() { - activePoliciesList = []; - }); - print('API request failed with status'); } } catch (e) { - print('Exception occurred: $e'); - } - } - - Future _loadMetabaseDashboard() async { - try { - final response = await http.get( - Uri.parse( - '${Environment.apiUrlPost.replaceAll("employeeRest/", "")}metaDashboardDemo?api=1', - ), - ); - - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - - _metabaseToken = json['data']['metabaseToken']; - _metabaseUrl = json['data']['metabaseUrl']; - - final htmlContent = _buildMetabaseHtml( - token: _metabaseToken!, - url: _metabaseUrl!, - ); - - final iframe = html.IFrameElement() - ..style.border = 'none' - ..style.width = '100%' - ..style.height = '100%' - ..style.minHeight = '100vh' - ..srcdoc = htmlContent; - - // ignore: undefined_prefixed_name - ui.platformViewRegistry.registerViewFactory( - 'metabase-dashboard', - (int viewId) => iframe, - ); - - setState(() { - _metabaseLoaded = true; - }); - } else { - throw Exception('Failed to load Metabase'); - } - } catch (e) { - ToastHelper.showErrorToast(context, 'Metabase loading failed'); - debugPrint(e.toString()); + debugPrint('Exception occurred: $e'); + } finally { + setState(() => isLoading = false); } } @@ -212,207 +139,93 @@ class _hrDashboardState extends State url: response['data']['metabaseUrl'], clientPolicyId: clientPolicyId, ); - - setState(() { - _metabaseLoaded = true; - }); + setState(() => _metabaseLoaded = true); } else { ToastHelper.showErrorToast(context, response['message']); } } catch (e) { ToastHelper.showErrorToast(context, 'Dashboard loading failed'); } finally { - setState(() { - isDashboardLoading = false; - }); + setState(() => isDashboardLoading = false); } } - - void _registerMetabaseIframe({ required String token, required String url, required String clientPolicyId, }) { - _dashboardViewType = 'metabase-dashboard-$clientPolicyId'; + final viewType = 'metabase-dashboard-$clientPolicyId'; + _dashboardViewType = viewType; - final htmlContent = _buildMetabaseHtml( - token: token, - url: url, - ); + if (_registeredViewTypes.contains(viewType)) return; - final iframe = html.IFrameElement() - ..style.border = 'none' - ..style.width = '100%' - ..style.height = '100%' - ..style.minHeight = '100vh' - ..srcdoc = htmlContent; + final embedUrl = "$url/embed/dashboard/$token#theme=light&bordered=true&titled=true"; - // ignore: undefined_prefixed_name ui.platformViewRegistry.registerViewFactory( - _dashboardViewType, - (int viewId) => iframe, + viewType, + (int viewId) => html.IFrameElement() + ..src = embedUrl + ..style.border = 'none' + ..style.width = '100%' + ..style.height = '100%' + ..allowFullscreen = true, ); - } - - - - 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: () => apiService.logout(), - child: Text("Logout"), - ), - ], - ), - ) ?? - false; + _registeredViewTypes.add(viewType); } @override Widget build(BuildContext context) { - return BaseLayout( - child: _buildContent(context), - ); + return BaseLayout(child: _buildContent(context)); } Widget _buildContent(BuildContext context) { return PopScope( canPop: false, - onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - - if (logout) { - await tokenService.clearAll(); - if (!mounted) return; - Navigator.pushNamedAndRemoveUntil( - context, - 'hrLogin', - (route) => false, - ); - } - }, child: Scaffold( - body: Expanded( + // βœ… REMOVED 'Expanded' from directly inside body. + body: Container( + width: double.infinity, + height: double.infinity, child: isDashboardLoading - ? Center( - child: Image.asset( - 'assets/nhance-loader.gif', - height: 60, - width: 60, - ), - ) + ? _buildLoader() : _metabaseLoaded - ? (postModules.isNotEmpty && activePoliciesList.isEmpty) ? - Padding( - padding: EdgeInsets.all(16), - child: Text( - 'No dashboard data available for your account', - style: TextStyle(color: Colors.grey), - ), - ) : Container( - decoration: BoxDecoration( - // color: Colors.red.shade50, - // color: Colors.white, - borderRadius: BorderRadius.circular(10), // πŸ‘ˆ set your desired radius - ), - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - // πŸ”Ή TOP BAR (always clickable) - Material( - elevation: 2, - color: Colors.white, - child: _buildPolicySelector(), - ), -SizedBox(height: 20), - // πŸ”Ή DASHBOARD AREA (isolated) - Expanded( - child: Stack( - children: [ - Positioned.fill( - child: _buildDashboard(), - ), - ], - ), - ), - ], - ) - - ) - : const Center( - child: Text( - 'No dashboard data available', - style: TextStyle(color: Colors.grey), - ), - ), + ? _buildDashboardView() + : _buildEmptyState(), ), - - ), ); } - Widget _buildPolicySelector() { - return Container( - padding: const EdgeInsets.all(16), - color: Colors.white, - child: Row( + Widget _buildDashboardView() { + if (postModules.isNotEmpty && activePoliciesList.isEmpty) { + return const Center(child: Text('No dashboard data available for your account')); + } + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( children: [ - const Text( - 'Select Policy', - style: TextStyle(fontWeight: FontWeight.w600), + // Policy Selector + Material( + elevation: 2, + borderRadius: BorderRadius.circular(8), + child: _buildPolicySelector(), ), - const SizedBox(width: 12), - SizedBox( - width: 420, - child: DropdownButtonFormField( - focusNode: _policyFocusNode, - value: selectedPolicyId, - isExpanded: true, - items: activePoliciesList.map((policy) { - return DropdownMenuItem( - value: policy['client_policy_id'].toString(), - child: Text( - '${policy['type']} - ${policy['policy_no']}', - overflow: TextOverflow.ellipsis, - ), - ); - }).toList(), - - onTap: () { - setState(() => isDropdownOpen = true); - }, - - onChanged: isDashboardLoading - ? null - : (value) { - if (value == selectedPolicyId) return; - - setState(() { - selectedPolicyId = value!; - }); - - _loadDashboardByPolicy(value!); - }, - - - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + const SizedBox(height: 50), + // Dashboard Area + Expanded( + child: IgnorePointer( + ignoring: _isPolicyDropdownOpen, // πŸ”₯ KEY FIX + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Colors.grey.shade200), + ), + child: HtmlElementView( + key: ValueKey(_dashboardViewType), + viewType: _dashboardViewType, ), ), ), @@ -422,69 +235,240 @@ SizedBox(height: 20), ); } - Widget _buildDashboard() { - if (isDashboardLoading) { - return Center( - child: Image.asset( - 'assets/nhance-loader.gif', - height: 60, - width: 60, - ), - ); - } + // Widget _buildPolicySelector() { + // return Container( + // padding: const EdgeInsets.all(16), + // color: Colors.white, + // child: Row( + // children: [ + // const Text( + // 'Select Policy', + // style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13), + // ), + // const SizedBox(width: 12), + // SizedBox( + // width: 420, + // height: 40, + // child: SearchAnchor( + // builder: (BuildContext context, SearchController controller) { + // // --- Logic to find the current display text manually --- + // String displayText = "Select Policy"; + // if (selectedPolicyId != null) { + // try { + // final currentPolicy = activePoliciesList.firstWhere( + // (p) => p['client_policy_id'].toString() == selectedPolicyId, + // ); + // displayText = "${currentPolicy['type']} - ${currentPolicy['policy_no']}"; + // } catch (e) { + // displayText = "Select Policy"; + // } + // } + // + // return InkWell( + // onTap: () => controller.openView(), + // child: Container( + // padding: const EdgeInsets.symmetric(horizontal: 12), + // decoration: BoxDecoration( + // border: Border.all(color: Colors.grey.shade300), + // borderRadius: BorderRadius.circular(8), + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // displayText, + // style: const TextStyle(fontSize: 12), + // ), + // const Icon(Icons.arrow_drop_down, color: Colors.white), + // ], + // ), + // ), + // ); + // }, + // suggestionsBuilder: (BuildContext context, SearchController controller) { + // final String input = controller.value.text.toLowerCase(); + // + // return activePoliciesList + // .where((policy) => + // policy['type'].toString().toLowerCase().contains(input) || + // policy['policy_no'].toString().toLowerCase().contains(input)) + // .map((policy) { + // final String displayLabel = "${policy['type']} - ${policy['policy_no']}"; + // + // return ListTile( + // title: Text(displayLabel, style: const TextStyle(fontSize: 13)), + // onTap: () { + // setState(() { + // selectedPolicyId = policy['client_policy_id'].toString(); + // controller.closeView(displayLabel); + // }); + // _loadDashboardByPolicy(selectedPolicyId!); + // }, + // ); + // }).toList(); + // }, + // ), + // ), + // ], + // ), + // ); + // } - if (!_metabaseLoaded || _dashboardViewType.isEmpty) { - return const Center( - child: Text( - 'No dashboard data available', - style: TextStyle(color: Colors.grey), - ), - ); - } + // Widget _buildPolicySelector() { + // return Container( + // padding: const EdgeInsets.all(16), + // child: Row( + // children: [ + // const Text('Select Policy', style: TextStyle(fontWeight: FontWeight.w600)), + // const SizedBox(width: 12), + // SizedBox( + // width: 420, + // child: DropdownButtonFormField( + // value: selectedPolicyId, + // isExpanded: true, + // items: activePoliciesList.map((policy) { + // return DropdownMenuItem( + // value: policy['client_policy_id'].toString(), + // child: Text('${policy['type']} - ${policy['policy_no']}', overflow: TextOverflow.ellipsis), + // ); + // }).toList(), + // onChanged: isDashboardLoading ? null : (value) { + // if (value == null || value == selectedPolicyId) return; + // setState(() => selectedPolicyId = value); + // _loadDashboardByPolicy(value); + // }, + // decoration: InputDecoration( + // contentPadding: const EdgeInsets.symmetric(horizontal: 10), + // border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + // ), + // ), + // ), + // ], + // ), + // ); + // } - return HtmlElementView(viewType: _dashboardViewType); + Widget _buildPolicySelector() { + return Container( + padding: const EdgeInsets.all(16), + color: Colors.white, + child: Row( + children: [ + const Text( + 'Select Policy', + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 13), + ), + const SizedBox(width: 12), + SizedBox( + width: 420, + height: 40, + child: SearchAnchor( + viewBackgroundColor: Colors.white, + viewConstraints: const BoxConstraints(maxHeight: 220), + + builder: (BuildContext context, SearchController controller) { + String displayText = "Select Policy"; + + if (selectedPolicyId != null) { + final policy = activePoliciesList.firstWhere( + (p) => p['client_policy_id'].toString() == selectedPolicyId, + orElse: () => {}, + ); + if (policy.isNotEmpty) { + displayText = "${policy['type']} - ${policy['policy_no']}"; + } + } + + return InkWell( + onTap: () { + setState(() => _isPolicyDropdownOpen = true); // πŸ”₯ OPEN + controller.openView(); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + displayText, + style: const TextStyle(fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ), + const Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ); + }, + + suggestionsBuilder: + (BuildContext context, SearchController controller) { + final input = controller.text.toLowerCase(); + + return activePoliciesList + .where((policy) => + policy['type'] + .toString() + .toLowerCase() + .contains(input) || + policy['policy_no'] + .toString() + .toLowerCase() + .contains(input)) + .map((policy) { + final label = + "${policy['type']} - ${policy['policy_no']}"; + + return ListTile( + dense: true, + title: Text(label, style: const TextStyle(fontSize: 13)), + onTap: () { + setState(() { + selectedPolicyId = + policy['client_policy_id'].toString(); + _isPolicyDropdownOpen = false; // πŸ”₯ CLOSE + }); + + controller.closeView(label); + _loadDashboardByPolicy(selectedPolicyId!); + }, + ); + }).toList(); + }, + ), + ), + ], + ), + ); } + Widget _buildLoader() => Center( + child: Image.asset('assets/nhance-loader.gif', height: 60, width: 60), + ); - String _buildMetabaseHtml({ - required String token, - required String url, - }) { - return ''' - - - - - Metabase Dashboard + Widget _buildEmptyState() => const Center( + child: Text('No dashboard data available', style: TextStyle(color: Colors.grey)), + ); - - - - - - - - - - - -'''; + Future _showLogoutDialog() async { + return await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Confirm Logout"), + content: const Text("Do you want to logout?"), + actions: [ + TextButton(onPressed: () => Navigator.pop(context, false), child: const Text("Cancel")), + TextButton(onPressed: () => Navigator.pop(context, true), child: const Text("Logout")), + ], + ), + ) ?? + false; } - - } \ No newline at end of file diff --git a/lib/presentation/hrPolicyDetails.dart b/lib/presentation/hrPolicyDetails.dart index 3dcdaba..fe3f5c1 100755 --- a/lib/presentation/hrPolicyDetails.dart +++ b/lib/presentation/hrPolicyDetails.dart @@ -2,6 +2,7 @@ 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'; @@ -91,7 +92,6 @@ class _HrPolicyDetailsState extends State List> originalData = []; // Original data source List> filteredData = []; // Filtered data source - dynamic argumentsData; dynamic policyType; dynamic policyName; @@ -114,7 +114,6 @@ class _HrPolicyDetailsState extends State /// ids shown in the current page (for header checkbox) List currentPageIds = []; - List get _paginatedData { final startIndex = (_currentPage - 1) * _rowsPerPage; final endIndex = @@ -130,21 +129,35 @@ class _HrPolicyDetailsState extends State 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 + // + // print("_PreEnrollmentState 1"); + // getCDPoliciesDetails(); + // print('allowed_modules'); + // } @override void initState() { super.initState(); - apiService = ApiService(context); // Initialize ApiService here - - print("_PreEnrollmentState 1"); + apiService = ApiService(context); + // If you are using TabBar, you MUST initialize this: + _tabController = TabController(length: 2, vsync: this); getCDPoliciesDetails(); - print('allowed_modules'); } - // Future _loadToken() async { // _postPreToken = tokenService.getCurrentToken(); // if(widget.TokenType == "post") { @@ -161,7 +174,7 @@ class _HrPolicyDetailsState extends State // getCDPoliciesDetails(); // } - Future getCDPoliciesDetails() async { + Future getCDPoliciesDetails_06FEB() async { print('9'); setState(() { isLoading = true; @@ -210,7 +223,7 @@ class _HrPolicyDetailsState extends State // ToastHelper.showWarningToast( // context, 'Request failed with status: ${response.statusCode}'); - print('Request failed with status: ${response['code']}'); + // print('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { @@ -219,11 +232,88 @@ class _HrPolicyDetailsState extends State print('Exception occurred: $e'); } finally { setState(() { - // _isLoading = false; + _isLoading = false; }); } } + Future getCDPoliciesDetails() async { + print('getCDPoliciesDetails started'); + setState(() { + isLoading = true; + }); + + try { + print('Fetching modules...'); + modulesString = await tokenService.readValue('empAllowed_modules'); + print("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; + } + + print('Calling API with TokenType: ${widget.TokenType}'); + print( + 'ClientId: ${widget.ClientId}, ClientPoliyId: ${widget.ClientPoliyId}'); + + final response = widget.TokenType == "post" + ? await apiService.getEmployeeAndDependenceToApi(widget.ClientId, + widget.ClientPoliyId, widget.clientBranchId, widget.Token) + : await apiService.getEmployeeAndDependenceToApiPre(widget.ClientId, + widget.ClientPoliyId, widget.clientBranchId, widget.Token!); + + print('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; + filteredData = List.from(originalData); + isLoading = false; + }); + } else { + setState(() => isLoading = false); + } + + } else { + final errorCode = response?['code'] ?? 'Unknown'; + // final errorMessage = response?['message'] ?? 'Request failed'; + // print('❌ Request failed - Code: $errorCode, Message: $errorMessage'); + + setState(() { + isLoading = false; + }); + + if (mounted) { + // ToastHelper.showErrorToast(context, errorMessage); + } + } + } catch (e, stackTrace) { + print('❌ Exception occurred: $e'); + print('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 = { @@ -351,8 +441,9 @@ class _HrPolicyDetailsState extends State final bytes = utf8.encode(csvData); final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); + final String csvFileName = "policies(${widget.cardPolicyNo}).csv"; final anchor = html.AnchorElement(href: url) - ..setAttribute("download", "CD_Policies.csv") + ..setAttribute("download", csvFileName) ..click(); html.Url.revokeObjectUrl(url); handleExportAction(); @@ -401,14 +492,13 @@ class _HrPolicyDetailsState extends State return value[0].toUpperCase() + value.substring(1).toLowerCase(); } - - Future getEcardBulkDownload() async { try { final emp_policy_ids = selectedEmployeeIds.toList(); print('10 $emp_policy_ids'); empHrId = await tokenService.readValue('empHrId'); - final response = await apiService.getEcardBulkDownloadApi('',empHrId,emp_policy_ids,widget.Token); + final response = await apiService.getEcardBulkDownloadApi( + '', empHrId, emp_policy_ids, widget.Token); if (response['status'] == true) { print('Request success'); _showBulkDownloadSuccessPopup(response['message']); @@ -458,9 +548,10 @@ class _HrPolicyDetailsState extends State borderRadius: BorderRadius.circular(8), ), ), - child: Text('OK',style: GoogleFonts.poppins( - color: Colors.white - ),), + child: Text( + 'OK', + style: GoogleFonts.poppins(color: Colors.white), + ), ), ), ], @@ -484,301 +575,612 @@ class _HrPolicyDetailsState extends State } Widget _buildContent(BuildContext context) { - return isLoading - ? Container( - color: Color(0x98FFFCE5), // 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 - ), - ) - : Container( - // padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50), - child: Column( + return Scaffold( + body: SafeArea( + child: Stack( + children: [ + // ===================== MAIN CONTENT ===================== + Column( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - /// πŸ”™ Back + Title (LEFT) + // ---------------- HEADER ---------------- + Padding( + padding: const EdgeInsets.all(16), + child: Column(children: [ Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - IconButton( - onPressed: () => {Navigator.pop(context)}, - icon: const Icon( - Icons.arrow_back_ios, - size: 18, - color: Colors.black, - ), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), + /// πŸ”™ Back + Title (LEFT) + Row( + children: [ + IconButton( + tooltip: 'Previous Page', + 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( + widget.TokenType == '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, + ), + ), + ], + ), + ), + ], ), - const SizedBox(width: 6), + + /// Push right content to end + const Spacer(), + + /// πŸ” Search Box 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( - widget.TokenType == '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, - ), - ), - ], + 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), + ), ), ), - ], - ), - /// Push right content to end - const Spacer(), + if (widget.is_ecard_bulk_download_for_employee == + 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, + )), + ), + ], - /// πŸ” 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), - ), - ), - ), + const SizedBox(width: 12), - if (widget.is_ecard_bulk_download_for_employee == 1) ...[ - const SizedBox(width: 12), - SizedBox( - width: 40, - height: 37, - child: ElevatedButton( + SizedBox( + width: 116, + height: 37, + child: ElevatedButton( onPressed: () { - getEcardBulkDownload(); + Navigator.push( + context, + MaterialPageRoute( + settings: widget.TokenType != "post" ? RouteSettings(name: 'preFileUpload') : RouteSettings(name: 'postFileUpload'), + builder: (context) => widget.TokenType != + "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, + + // Token: widget.Token, + // 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, - 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) => widget.TokenType != "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, - - // Token: widget.Token, - // 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 - 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 (widget.TokenType == "pre") ...[_buildStatusSummary()], - if (widget.TokenType == "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), + 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, + ), + ), + ), + ), + ], + ), + ]), ), - 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, + + 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 (widget.TokenType == "pre") + Padding( + padding: const EdgeInsets.only(left: 15), + child: _buildStatusSummary(), + ), + + if (widget.TokenType == "post") + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF9EBBD), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + 'Premium - β‚Ή${widget.total_premium}', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: const Color(0xFF009195), + ), + ), + ), + ], ), ), + + 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 + ), + ) + : SingleChildScrollView( + child: _buildCDDataTable(context), + ), + ), ), + + 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( + // widget.TokenType == '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 (widget.is_ecard_bulk_download_for_employee == 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) => widget.TokenType != "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, + // + // // Token: widget.Token, + // // 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 + // 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 (widget.TokenType == "pre") ...[_buildStatusSummary()], + // if (widget.TokenType == "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 getFixedStatusCounts() { int draftCount = 0; int enrolledCount = 0; @@ -788,14 +1190,14 @@ class _HrPolicyDetailsState extends State if (status == 'draft') { draftCount++; - } else if (status == 'enrolled') { + } else if (status == 'under process') { enrolledCount++; } } return { 'draft': draftCount, - 'enrolled': enrolledCount, + 'under process': enrolledCount, 'total': filteredData.length, }; } @@ -806,7 +1208,7 @@ class _HrPolicyDetailsState extends State } final statusCounts = getFixedStatusCounts(); - final List order = ['draft', 'enrolled', 'total']; + final List order = ['draft', 'under process', 'total']; return SizedBox( height: 34, @@ -860,7 +1262,7 @@ class _HrPolicyDetailsState extends State // height: 50, child: Center( child: Text( - 'No available data', + 'No data is available for the selected policy', style: GoogleFonts.poppins( color: Colors.grey, fontWeight: FontWeight.w400, @@ -874,8 +1276,6 @@ class _HrPolicyDetailsState extends State .whereType() .toList(); - - return Container( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -906,7 +1306,6 @@ class _HrPolicyDetailsState extends State }); }, ), - ), Expanded( flex: 3, @@ -1050,7 +1449,6 @@ class _HrPolicyDetailsState extends State }); }, ), - ), Expanded( @@ -1479,6 +1877,7 @@ class _HrPolicyDetailsState extends State // Previous button IconButton( + tooltip: 'Previous Page', onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, diff --git a/lib/presentation/policies.dart b/lib/presentation/policies.dart index 475779e..93a18c7 100644 --- a/lib/presentation/policies.dart +++ b/lib/presentation/policies.dart @@ -12,6 +12,7 @@ import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart'; import 'package:nhancepolicy/presentation/claims.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'; @@ -298,7 +299,7 @@ class _policiesState extends State Text( message, textAlign: TextAlign.center, - style: const TextStyle( + style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), @@ -331,12 +332,16 @@ class _policiesState extends State Widget build(BuildContext context) { return BaseLayout( child: PopScope( - canPop: false, // 🚫 block default back + canPop: false, onPopInvoked: (didPop) async { - bool logout = await _showLogoutDialog(); - if (logout) { + if (didPop) return; + + final shouldLogout = await _showLogoutDialog(); + + if (shouldLogout) { await apiService.logout(); if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( context, 'hrLogin', @@ -369,18 +374,18 @@ class _policiesState extends State 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(), - ), + // 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', @@ -396,7 +401,7 @@ class _policiesState extends State SizedBox(height: 15), Container( width: double.infinity, - height: 300, + height: 400, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, @@ -405,28 +410,21 @@ class _policiesState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - /// ================= OPEN FOR ENROLLMENT ================= - const Text( + Text( 'Open for Enrollment', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600), ), - const SizedBox(height: 14), + SizedBox(height: 14), - Container( - width: double.infinity, - // padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - // color: Colors.white, - borderRadius: BorderRadius.circular(6), - // border: Border.all(color: Colors.black12), - ), + /// βœ… SCROLLABLE AREA + Expanded( child: openEnrollment.isEmpty ? _EmptyBox('No policies open for enrollment') : _PolicyGrid( - policies: openEnrollment, isEnrollment: true), + policies: openEnrollment, + isEnrollment: true, + ), ), - - const SizedBox(height: 24), ], ), ), @@ -435,7 +433,7 @@ class _policiesState extends State SizedBox(height: 20), Container( width: double.infinity, - height: 300, + height: 400, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, @@ -444,14 +442,13 @@ class _policiesState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - /// ================= ACTIVE POLICIES HEADER ================= + /// HEADER Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( + Text( 'Active Policies', - style: TextStyle( - fontSize: 14, fontWeight: FontWeight.w600), + style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600), ), _ActiveExpiredToggle( selectedIndex: selectedIndex, @@ -460,13 +457,6 @@ class _policiesState extends State selectedIndex = value; stausVal = value == 1 ? 1 : 0; }); - - empClientId = - await tokenService.readValue('empClientId'); - empClientBranchId = - await tokenService.readValue('empClientBranchId'); - empHrId = await tokenService.readValue('empHrId'); - await getPostCashDepositDetails( empClientBranchId, empClientId, @@ -479,36 +469,29 @@ class _policiesState extends State ), const SizedBox(height: 14), - Container( - width: double.infinity, - // padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - // color: Colors.white, - borderRadius: BorderRadius.circular(6), - // border: Border.all(color: Colors.black12), - ), + /// βœ… SCROLLABLE GRID + Expanded( child: activePolicies.isEmpty - ? _EmptyBox('No Active policies') + ? stausVal == 0 ? _EmptyBox('You don’t have any expired policies at the moment.') : _EmptyBox('No active policies found') : _PolicyGrid( - policies: activePolicies, - isEnrollment: false,onBulkDownload: (clientPolicyId) { - getEcardBulkDownload(clientPolicyId); - } + policies: activePolicies, + isEnrollment: false, + onBulkDownload: getEcardBulkDownload, ), ), - const SizedBox(height: 10), - - const Align( + const SizedBox(height: 8), + Align( alignment: Alignment.bottomRight, child: Text( '* Premium may vary subject to claims', - style: TextStyle(fontSize: 10, color: Colors.red), + style: GoogleFonts.poppins(fontSize: 10, color: Colors.red), ), ), ], ), ), + ] ], @@ -518,6 +501,16 @@ class _policiesState extends State } } +class ResponsiveGridConfig { + final int crossAxisCount; + final double childAspectRatio; + + const ResponsiveGridConfig( + this.crossAxisCount, + this.childAspectRatio, + ); +} + class _PolicyGrid extends StatelessWidget { final List> policies; final bool isEnrollment; @@ -530,18 +523,37 @@ class _PolicyGrid extends StatelessWidget { this.onBulkDownload, }); + 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); + } + } + + @override Widget build(BuildContext context) { final tokenService = TokenStorageService(); + final config = _getGridConfig(context, isEnrollment); return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.zero, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, // desktop + crossAxisCount: config.crossAxisCount, + childAspectRatio: config.childAspectRatio, crossAxisSpacing: 16, mainAxisSpacing: 16, - childAspectRatio: isEnrollment ? 2.8 : 2.2, ), itemCount: policies.length, itemBuilder: (context, index) { @@ -550,78 +562,84 @@ class _PolicyGrid extends StatelessWidget { 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'); + onTap: () async { + final token = await tokenService.getCurrentToken(); + final clientId = + await tokenService.readValue('enrollmentClient_id'); + final branchId = await tokenService + .readValue('enrollmentEmpClientBranchId'); - // βœ… SAFETY CHECK - if (token == null || - enrollmentClientId == null || - enrollmentBranchId == null) { - debugPrint('❌ Missing required data for navigation ${token}'); - return; - } + if (token == null || clientId == null || branchId == null) { + 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 - ), + 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, onBulkDownload: onBulkDownload, onTap: () async { - final String? token = await tokenService.getCurrentToken(); - final String? empClientId = await tokenService.readValue('empClientId'); - final String? empBranchId = await tokenService.readValue('empClientBranchId'); + final token = await tokenService.getCurrentToken(); + final clientId = + await tokenService.readValue('empClientId'); + final branchId = + await tokenService.readValue('empClientBranchId'); - // βœ… SAFETY CHECK - if (token == null || - empClientId == null || - empBranchId == null) { - debugPrint('❌ Missing required data for navigation ${token}'); + if (token == null || clientId == null || branchId == null) { return; } - print('$token , $empClientId, $empBranchId'); - print(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, + 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'], + 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'], ), ), ); @@ -632,6 +650,120 @@ class _PolicyGrid extends StatelessWidget { } } + +// 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; +// } +// +// print('$token , $empClientId, $empBranchId'); +// print(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; @@ -658,31 +790,45 @@ class _EnrollmentPolicyCardNew extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Policy Number - Text( - data['policy_no'] ?? '', - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - ), - ), + 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), + // 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, + // ), + // ), + // ), - /// Insurer - Text( - data['insurer_name'] ?? '', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - - const SizedBox(height: 4), + const SizedBox(height: 10), /// Closes On Text( 'Closes on: ${data['policy_expiry_date'] ?? ''}', - style: const TextStyle( + style: GoogleFonts.poppins( fontSize: 12, color: Colors.red, ), @@ -700,7 +846,7 @@ class _EnrollmentPolicyCardNew extends StatelessWidget { color: Colors.orange, ), _StatusPillCount( - label: 'Enrolled', + label: 'Under Process', value: data['membersCountOfEnrolled'] ?? 0, color: Colors.blue, ), @@ -782,8 +928,6 @@ class _ActivePolicyCardNew extends StatelessWidget { ), ), ), - - ], ), @@ -795,10 +939,10 @@ class _ActivePolicyCardNew extends StatelessWidget { children: [ Expanded( child: Text( - data['policy_no'] ?? '', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, + '${data['type']} - ${data['policy_no']}', + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), @@ -808,12 +952,19 @@ class _ActivePolicyCardNew extends StatelessWidget { const SizedBox(height: 4), - /// INSURER - Text( - data['insurer_name'] ?? '', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, + /// 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, + ), ), ), @@ -822,7 +973,7 @@ class _ActivePolicyCardNew extends StatelessWidget { /// DATE RANGE Text( '${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}', - style: const TextStyle( + style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF8A9B0F), ), @@ -878,12 +1029,12 @@ class _StatusPillCount extends StatelessWidget { children: [ Text( label, - style: TextStyle(fontSize: 13, color: color), + style: GoogleFonts.poppins(fontSize: 13, color: color), ), const SizedBox(width: 8), Text( value.toString(), - style: TextStyle( + style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.bold, color: color, @@ -953,7 +1104,7 @@ class _ToggleItem extends StatelessWidget { ), child: Text( label, - style: TextStyle( + style: GoogleFonts.poppins( fontSize: 11, color: active ? Colors.white : Colors.black, fontWeight: FontWeight.w500, @@ -980,7 +1131,7 @@ class _EmptyBox extends StatelessWidget { border: Border.all(color: Colors.black12), ), child: Center( - child: Text(text, style: const TextStyle(color: Colors.grey)), + child: Text(text, style: GoogleFonts.poppins(color: Colors.grey)), ), ); } diff --git a/lib/presentation/postFileUpload.dart b/lib/presentation/postFileUpload.dart index edfcc88..1aed3c4 100755 --- a/lib/presentation/postFileUpload.dart +++ b/lib/presentation/postFileUpload.dart @@ -246,8 +246,11 @@ class _postFileUploadState extends State { Future getHrFileDownload(id, file_name) async { // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); + print("**********-------*****"); + final encryptClientId = widget.ClientId; + print(encryptClientId); final apiurl = Environment.apiUrlPost; - final String url = '$apiurl/hrFileDownload?id=$id'; + final String url = '$apiurl/hrFileDownload?id=$id&cliend_id=$encryptClientId'; final token = widget.Token; final response = await http.get( @@ -548,6 +551,7 @@ class _postFileUploadState extends State { Row( children: [ IconButton( + tooltip: 'Previous Page', onPressed: () => {Navigator.pop(context)}, icon: const Icon( Icons.arrow_back_ios, @@ -1143,6 +1147,7 @@ class _postFileUploadState extends State { // Previous button IconButton( + tooltip: 'Previous Page', onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, diff --git a/lib/presentation/preFileUpload.dart b/lib/presentation/preFileUpload.dart index a430ad8..b9fa36b 100755 --- a/lib/presentation/preFileUpload.dart +++ b/lib/presentation/preFileUpload.dart @@ -734,6 +734,7 @@ class _excelVerifyState extends State { Row( children: [ IconButton( + tooltip: 'Previous Page', onPressed: () => {Navigator.pop(context)}, icon: const Icon( Icons.arrow_back_ios, diff --git a/lib/service/_ResponsiveGridConfig.dart b/lib/service/_ResponsiveGridConfig.dart new file mode 100644 index 0000000..7a92603 --- /dev/null +++ b/lib/service/_ResponsiveGridConfig.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class _ResponsiveGridConfig { + final int crossAxisCount; + final double childAspectRatio; + + _ResponsiveGridConfig(this.crossAxisCount, this.childAspectRatio); +} + +_ResponsiveGridConfig _getGridConfig( + BuildContext context, + bool isEnrollment, + ) { + final width = MediaQuery.of(context).size.width; + + if (width < 600) { + // Mobile + return _ResponsiveGridConfig( + 1, + isEnrollment ? 1.25 : 1.15, + ); + } else if (width < 900) { + // Tablet + return _ResponsiveGridConfig( + 2, + isEnrollment ? 1.6 : 1.45, + ); + } else if (width < 1200) { + // Small desktop + return _ResponsiveGridConfig( + 3, + isEnrollment ? 2.1 : 1.9, + ); + } else { + // Large desktop + return _ResponsiveGridConfig( + 4, + isEnrollment ? 2.8 : 2.4, + ); + } +} diff --git a/lib/service/svg_service.dart b/lib/service/svg_service.dart index c02046b..2f07015 100644 --- a/lib/service/svg_service.dart +++ b/lib/service/svg_service.dart @@ -19,22 +19,22 @@ class SvgService { '''; static const String claims = ''' - - - - - - - - - - + + + '''; static const String logout = ''' - - + + + + + + + + + ''';