diff --git a/assets/hrLogin.png b/assets/hrLogin.png new file mode 100644 index 0000000..e494a9e Binary files /dev/null and b/assets/hrLogin.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..4dcfde9 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - shared_preferences: true \ No newline at end of file diff --git a/lib/branch/branch_selection_page.dart b/lib/branch/branch_selection_page.dart index 9773cac..5af22df 100755 --- a/lib/branch/branch_selection_page.dart +++ b/lib/branch/branch_selection_page.dart @@ -1,10 +1,11 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; + import '../customAppBar/base_layout.dart'; import '../customAppBar/customAppBar.dart'; import '../customAppBar/customFooter.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'; @@ -18,12 +19,14 @@ class BranchSelectionPage extends StatefulWidget { class _BranchSelectionPageState extends State { List> branches = []; + late ApiService apiService; int? selectedIndex; final tokenStorage = TokenStorageService(); @override void initState() { super.initState(); + apiService = ApiService(context); _loadBranches(); } @@ -42,7 +45,7 @@ class _BranchSelectionPageState extends State { Future _handleNext() async { if (selectedIndex == null) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( + const SnackBar( content: Text('Please select a branch'), backgroundColor: Colors.orange, ), @@ -52,110 +55,72 @@ class _BranchSelectionPageState extends State { final selectedBranch = branches[selectedIndex!]; - - // Save selected branch and decode token + // Save selected branch & decode token securely await tokenStorage.saveSelectedBranch(selectedBranch); - // Debug: Print decoded token final decodedToken = tokenStorage.getDecodedToken(); - print('Selected Branch: $selectedBranch'); - print('Decoded Token: $decodedToken'); + final token = tokenStorage.getCurrentToken(); - - - // Navigate to home - if (mounted) { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - - //Post Token Decode Details - final empClientBranchId = decodedToken?['post_branch_id']; - await prefs.setString('empClientBranchId', empClientBranchId); - - final empPrimaryId = decodedToken?['post_hr_id']; - await prefs.setString('empPrimaryId', empPrimaryId); - - final empClientId = decodedToken?['post_client_id']; - await prefs.setString('empClientId', empClientId); - - final empHrId = decodedToken?['post_hr_id']; - await prefs.setString('empHrId', empHrId); - - dynamic allowedModules = decodedToken?['allowed_modules']; - -// If it's a String, decode it again - if (allowedModules is String) { - allowedModules = jsonDecode(allowedModules); - } - - final empAllowedModules = allowedModules['post']; - - await prefs.setString( - 'empAllowed_modules', jsonEncode(empAllowedModules)); - - // final empAllowed_modules = decodedToken?['allowed_modules'][0]['post']; - // await prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules)); - - //Pre Token Decode Details - final enrollmentEmpClientBranchId = decodedToken?['pre_branch_id']; - await prefs.setString( - 'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId); - - final enrollmentEmpPrimaryId = decodedToken?['pre_hr_id']; - await prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId); - - final enrollmentClient_id = decodedToken?['pre_client_id']; - await prefs.setString('enrollmentClient_id', enrollmentClient_id); - - final enrollmentHrId = decodedToken?['pre_hr_id']; - await prefs.setString('enrollmentHrId', enrollmentHrId); - - final enrollmentAllowedModules = allowedModules['pre']; - - await prefs.setString( - 'enrollmentAllowed_modules', jsonEncode(enrollmentAllowedModules)); - - // final enrollmentAllowed_modules = decodedToken?['allowed_modules'][1]['pre']; - // await prefs.setString('enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules)); - - final token = await tokenStorage.getCurrentToken(); - print('tokenbranch - $token'); - await prefs.setString('token', token!); - ToastHelper.showSuccessToast(context, 'Successfully Login'); - Navigator.pushReplacementNamed(context, 'hrDashboard'); + if (decodedToken == null || token == null) { + ToastHelper.showErrorToast(context, 'Invalid token data'); + return; } - // final branch = branches[selectedIndex!]; - // // ===================== - // // POST (HR) - // // ===================== - // WebSession.postClientId = branch['client_id']?.toString(); - // WebSession.postBranchId = - // branch['post_branch_id']?.toString() ?? - // branch['client_branch_id']?.toString(); - // WebSession.postHrId = branch['id']?.toString(); - // WebSession.postModules = - // branch['allowed_modules']?['post'] ?? []; - // - // // ===================== - // // PRE (Enrollment) - // // ===================== - // WebSession.preClientId = branch['client_id']?.toString(); - // WebSession.preBranchId = - // branch['pre_branch_id']?.toString() ?? - // branch['client_branch_id']?.toString(); - // WebSession.preHrId = branch['id']?.toString(); - // WebSession.preModules = - // branch['allowed_modules']?['pre'] ?? []; - // + // 🔐 Save decoded values securely + await tokenStorage.saveDecodedSessionData(decodedToken, token); + // ToastHelper.showSuccessToast(context, 'Successfully Login'); - // - // if (!mounted) return; - // Navigator.pushReplacementNamed(context, 'hrDashboard'); + + if (!mounted) return; + Navigator.pushReplacementNamed(context, 'policies'); + } + + 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; } @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, + ); + } + }, + child: _buildContent(context), + ), + ); + } + + Widget _buildContent(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final screenWidth = MediaQuery.of(context).size.width; @@ -189,8 +154,7 @@ class _BranchSelectionPageState extends State { childAspectRatio = 4; } return Scaffold( - backgroundColor: Color(0xFFEFF3F6), - appBar: CustomAppBar(), + backgroundColor: Color(0xFFF5F7F7), body: Column( children: [ Expanded( @@ -232,36 +196,36 @@ class _BranchSelectionPageState extends State { height: gridHeight, // Shows approximately 3 rows child: branches.isEmpty ? Center( - child: Text( - 'No branches available', - style: TextStyle( - fontSize: 16, color: Colors.grey), - ), - ) + child: Text( + 'No branches available', + style: TextStyle( + fontSize: 16, color: Colors.grey), + ), + ) : 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), - ); - }, - ), + // 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), + ); + }, + ), ), SizedBox(height: 24), Center( @@ -294,10 +258,6 @@ class _BranchSelectionPageState extends State { ), ), ), - Container( - width: double.infinity, - child: CustomFooter(), - ), ], ), ); diff --git a/lib/claimshistory.dart b/lib/claimshistory.dart deleted file mode 100755 index 681d18d..0000000 --- a/lib/claimshistory.dart +++ /dev/null @@ -1,656 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:nhancepolicy/service/api_service.dart'; - -class ClaimHistoryPopup extends StatefulWidget { - final String ticket_id; - final String empName; - final String empCode; - final String policyType; - final String clientPolicyNo; - final String claimAmount; - final String claimNo; - final String postToken; - - const ClaimHistoryPopup({ - Key? key, - required this.ticket_id, - required this.empName, - required this.empCode, - required this.policyType, - required this.clientPolicyNo, - required this.claimAmount, - required this.claimNo, - required this.postToken, - }) : super(key: key); - - @override - State createState() => _ClaimHistoryPopupState(); -} - -class _ClaimHistoryPopupState extends State { - late ApiService apiService; - List> getClaimsHistoryList = []; - List stepKeys = []; - late Map stepMap; - int _index = 4; - bool isLoading = false; - - @override - void initState() { - super.initState(); - apiService = ApiService(context); - print(widget.postToken); - print(widget.claimAmount); - print(widget.claimNo); - print(widget.clientPolicyNo); - print(widget.empCode); - print(widget.policyType); - print(widget.ticket_id); - getClaimsHistoryDetails(); - } - - @override - void dispose() { - super.dispose(); - } - - Future getClaimsHistoryDetails() async { - setState(() { - isLoading = true; - }); - try { - print('10'); - // final ticketID = widget.ticket_id; - // if (ticketID != '' || ticketID != null) { - // return; - // } - final response = await apiService.getClaimsHistoryToApi( - widget.ticket_id, widget.postToken); - if (response['status'] == 'success') { - setState(() { - isLoading = false; - }); - setState(() { - getClaimsHistoryList = [ - Map.from(response['data']['ticket_data']) - ]; - stepMap = getClaimsHistoryList[0]; - stepKeys = stepMap.keys.toList(); - print('Claims History List $getClaimsHistoryList'); - // originalData = getCDPolicies; - // filteredData = List.from(originalData); - // print('filteredData'); - // print(filteredData); - }); - } else { - setState(() { - isLoading = false; - }); - - // ToastHelper.showWarningToast( - // context, 'Request failed with status: ${response.statusCode}'); - print('Request failed with status: ${response['code']}'); - } - } catch (e) { - setState(() { - isLoading = false; - }); - print('Exception occurred: $e'); - } finally { - setState(() { - isLoading = false; - }); - } - } - - @override - Widget build(BuildContext context) { - // if (getClaimsHistoryList.isEmpty) { - // return SizedBox( - // height: 50, - // child: Center(child: Text('No available Claims')), - // ); - // } - return Container( - constraints: BoxConstraints(maxWidth: 800, maxHeight: 800), - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Text( - 'Claim History', - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Color(0xFF101010), - ), - ), - ), - ), - MouseRegion( - cursor: SystemMouseCursors.click, // Show pointer cursor - child: GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Color(0xFFBCBCBC)), - borderRadius: BorderRadius.circular(6), - ), - child: const Icon( - Icons.close, - size: 25, - color: Color(0xFFBCBCBC), - ), - ), - ), - ) - ], - ), - SizedBox(height: 10), - Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), - decoration: BoxDecoration( - color: Color(0xFFFFFFFF), // White background - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow( - color: Color(0xFFEBEBEB), // Shadow color - blurRadius: 14, // How soft the shadow is - spreadRadius: 2, // How much it spreads - offset: Offset(0, 1), // X and Y offset - ), - ], - ), - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: _buildKeyValue('Name', - '${widget.empName ?? ''} (${widget.empCode ?? ''})'), - ), - SizedBox(width: 16), - Expanded( - child: _buildKeyValue('Policy Name', - '${widget.policyType ?? ''} - ${widget.clientPolicyNo ?? ''}')), - ], - ), - SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildKeyValue( - 'Claim Amount', '₹${widget.claimAmount ?? ''}'), - ), - SizedBox(width: 16), - Expanded( - child: _buildKeyValue( - 'Claim Number', widget.claimNo ?? ''), - ), - ], - ), - ], - ), - ), - SizedBox(height: 10), - // Container( - // padding: const EdgeInsets.all(16), - // child: Stepper( - // currentStep: _index, - // onStepCancel: () { - // if (_index > 0) { - // setState(() { - // _index -= 1; - // }); - // } - // }, - // onStepContinue: () { - // if (_index < 4) { - // setState(() { - // _index += 1; - // }); - // } - // }, - // onStepTapped: (int index) { - // setState(() { - // _index = index; - // }); - // }, - // steps: [ - // Step( - // title: Text('Step 1: PAYMENT INITIATED'), - // content: Text('Pay Initiate Date: 05-05-2025'), - // isActive: true, - // state: StepState.complete, - // ), - // Step( - // title: Text('Step 2: INFORMATION REQUIRED'), - // content: Text('Raised Date: 05-05-2025'), - // isActive: true, - // state: StepState.complete, - // ), - // Step( - // title: Text('Step 3: CLAIM NO. UPDATION'), - // content: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text('Claim Number: 4315440'), - // Text('Registration Date: 05-05-2025'), - // ], - // ), - // isActive: true, - // state: StepState.complete, - // ), - // Step( - // title: Text('Step 4: QUERY DOCUMENT REQUIRED'), - // content: Text('Query Received Date: 05-05-2025'), - // isActive: true, - // state: StepState.complete, - // ), - // Step( - // title: Text('Step 5: APPROVED'), - // content: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text('Approved Amount: 4315440'), - // Text('Approved Date: 05-05-2025'), - // Text('Approved Letter: Lorem ipsum...'), - // Text('Description: Lorem ipsum...'), - // ], - // ), - // isActive: true, - // state: StepState.complete, - // ), - // ], - // ), - // ) - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: List.generate(5, (index) { - // return _buildStep( - // stepNumber: index + 1, - // title: _getStepTitle(index), - // content: _getStepContent(index), - // isLast: index == 4, - // ); - // }), - // ) - - 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( - child: getClaimsHistoryList.isNotEmpty - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: List.generate(stepKeys.length, (index) { - String stepTitleKey = stepKeys[index]; - Map stepData = - stepMap[stepTitleKey]; - - print('stepTitleKey'); - print(stepTitleKey); - print('stepData'); - print(stepData); - - Widget content = _getStepContentFromApi(stepData); - print('check'); - print(context); - - // // 🟡 Skip step if there's no valid content (e.g., ID NOT GENERATED case) - // if ((content as Column).children.isEmpty) { - // print( - // "Skipping step $stepTitleKey due to no valid content"); - // content = Text("No content available", - // style: TextStyle(color: Colors.grey)); - // // or `return Container()` - // } - - // if (content == null) { - // print( - // "Skipping step: $stepTitleKey due to no valid content"); - // return const SizedBox(); // Completely skip step - // } - - return _buildStep( - stepNumber: index + 1, - title: _getStepTitleFromApi( - stepTitleKey, stepData), - content: content, - - // content: _getStepContentFromApi(stepData), - isLast: index == stepKeys.length - 1, - ); - }), - ) - : Container( - height: MediaQuery.of(context).size.height * 0.4, - // color: Colors.red, - child: Center( - child: Column( - children: [ - Image.asset( - 'assets/claimsData.png', // Replace 'default_image.png' with your default image asset path - width: 350, - height: 350, - fit: BoxFit.cover, - ), - const Text( - 'No Available Claims', - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 15), - ), - ], - )), - ), - ) - ], - ), - ), - ); - } - - Widget _buildStep({ - required int stepNumber, - // required String title, - required Widget title, - required Widget content, - bool isLast = false, - }) { - print(title); - print("contentss - $content"); - final noContent; - if ((content as Column).children.isEmpty) { - noContent = 0; - } else { - noContent = 1; - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Left Column with circle + line - Column( - children: [ - // Add top spacing before circle - SizedBox(height: stepNumber == 1 ? 0 : 4), - - // Step number circle - Container( - height: 28, - width: 28, - decoration: BoxDecoration( - color: Color(0xFF00A5A8), - shape: BoxShape.circle, - ), - alignment: Alignment.center, - child: Text( - '$stepNumber', - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - ), - - // Dotted line below circle (except for last step) - if (!isLast) - Container( - height: noContent == 1 ? 70 : 25, // increase to extend line - width: 2, - margin: EdgeInsets.only(top: 4, bottom: 4), - child: CustomPaint( - painter: DottedLinePainter(), - ), - ), - ], - ), - - SizedBox(width: 12), - - // Right Side: Step title and content - - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - title, - if (noContent == 1) SizedBox(height: noContent == 0 ? 0 : 8), - if (noContent == 1) - Container( - width: double.infinity, - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - color: Color(0xFFF7F7F7), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Color(0xFFE0E0E0)), - ), - child: content, - ), - if (noContent == 1) SizedBox(height: isLast ? 0 : 16), - ], - ), - ), - ], - ); - } - - // String _getStepTitleFromApi(String status, Map data) { - // final modifiedBy = data['modified_by'] ?? ''; - // final modifiedAt = data['modified_at'] ?? ''; - // return '$status ($modifiedBy – $modifiedAt)'; - // } - - Widget _getStepTitleFromApi(String status, Map data) { - final modifiedBy = data['modified_by'] ?? ''; - final modifiedAt = data['modified_at'] ?? ''; - final symbol = (data['modified_by'] != null && data['modified_by'] != '') ? ' - ' : ''; - - return RichText( - text: TextSpan( - children: [ - TextSpan( - text: status, - style: GoogleFonts.poppins( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Color(0xFF212120), // Status color - ), - ), - TextSpan( - text: ' ($modifiedBy$symbol$modifiedAt)', - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFF565656), // Subtitle color - ), - ), - ], - ), - ); - } - - Widget _getStepContentFromApi(Map data) { - List rows = []; - - print("data - $data"); - - // bool hasDisplayFields(Map map) { - // for (var entry in map.entries) { - // if (entry.value is Map) { - // final innerMap = entry.value as Map; - // if (innerMap.containsKey('display_name') || - // innerMap.containsKey('display_value')) { - // print("display fields found"); - // return true; - // } - // } - // } - // return false; - // } - // - // if (!hasDisplayFields(data)) { - // print("❌ Skipping because no display fields found"); - // return SizedBox.shrink(); // or return an empty Container/Spacer if needed - // } - - data.forEach((key, value) { - // Skip metadata - if (key == 'modified_by' || key == 'modified_at') return; - - if (value is Map) { - final displayName = value['display_name']; - final displayValue = value['display_value']; - - if (displayName != null && displayValue != null) { - print("✅ displayName - $displayName, displayValue - $displayValue"); - rows.add(_buildHistoryListData(displayName, displayValue)); - rows.add(SizedBox(height: 6)); - } - } - }); - - // data.forEach((key, value) { - // // Skip metadata fields - // if (key == 'modified_by' || key == 'modified_at') return; - // print("ContentKey - $key"); - // print("Content - $value"); - // String displayName = value['display_name'] ?? key; - // String displayValue = value['display_value'] ?? 'N/A'; - // - // print("displayName - $displayName"); - // print("displayValue - $displayValue"); - // if (displayName == 'N/A' || displayValue == 'N/A') return; - // - // rows.add(_buildHistoryListData(displayName, displayValue)); - // rows.add(SizedBox(height: 6)); - // }); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: rows, - ); - } - - Widget _buildKeyValue(String title, String value) { - final displayValue = - (value == null || value.trim().isEmpty) ? 'N/A' : value; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: GoogleFonts.poppins( - color: Color(0xFF747474), - fontWeight: FontWeight.w400, - fontSize: 16, - ), - ), - SizedBox(height: 4), - Text( - displayValue, - style: GoogleFonts.poppins( - color: Color(0xFF000000), - fontWeight: FontWeight.w500, - fontSize: 16, - ), - ), - ], - ); - } - - Widget _buildHistoryListData(String title, String value) { - print("_buildHistoryListData"); - final displayValue = - (value == null || value.trim().isEmpty) ? 'N/A' : value; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), // optional spacing - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Title - Align to center left - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Text( - title, - style: GoogleFonts.poppins( - color: const Color(0xFF747474), - fontWeight: FontWeight.w400, - fontSize: 14, - ), - ), - ), - ), - - // Value - Align to center right - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Text( - displayValue, - style: GoogleFonts.poppins( - color: const Color(0xFF000000), - fontWeight: FontWeight.w400, - fontSize: 14, - ), - ), - ), - ), - ], - ), - ); - } -} - -class DottedLinePainter extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - const dashHeight = 2.0; - const dashSpace = 3.0; - double startY = 0; - final paint = Paint() - ..color = Colors.grey.shade400 - ..strokeWidth = 1; - - while (startY < size.height) { - canvas.drawLine( - Offset(0, startY), - Offset(0, startY + dashHeight), - paint, - ); - startY += dashHeight + dashSpace; - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => false; -} diff --git a/lib/customAppBar/base_layout.dart b/lib/customAppBar/base_layout.dart new file mode 100644 index 0000000..9dae729 --- /dev/null +++ b/lib/customAppBar/base_layout.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'top_app_bar.dart'; +import 'side_bar.dart'; + +class BaseLayout extends StatelessWidget { + final Widget child; + + const BaseLayout({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const NhanceTopBar(), + body: Row( + children: [ + const NhanceSideBar(), + Expanded( + child: Container( + color: const Color(0xFFF5F7F7), + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ], + ), + ); + } +} + diff --git a/lib/customAppBar/customAppBar.dart b/lib/customAppBar/customAppBar.dart index 768fc9b..dab3841 100755 --- a/lib/customAppBar/customAppBar.dart +++ b/lib/customAppBar/customAppBar.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:adaptive_navbar/adaptive_navbar.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/responsive.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:nhancepolicy/responsive.dart'; import '../service/api_service.dart'; +import '../service/token_storage_service.dart'; class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { @override @@ -18,6 +19,9 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { class _CustomAppBarState extends State { bool showBackToHR = false; late ApiService apiService; + final tokenService = TokenStorageService(); + // 🔐 Secure storage instance + static const FlutterSecureStorage _secureStorage = FlutterSecureStorage(); // bool isLoading = true; // Add a loading state // bool hideInactiveStatus = true; @@ -38,6 +42,7 @@ class _CustomAppBarState extends State { // Navigator.pushNamed(context, 'hrLogin'); } + @override Widget build(BuildContext context) { final sw = MediaQuery.of(context).size.width; @@ -89,24 +94,14 @@ class _CustomAppBarState extends State { NavBarItem( text: "Change Branch", onTap: () async { - final prefs = await SharedPreferences.getInstance(); - await prefs.remove('selected_branch'); - await prefs.remove('decoded_token'); - await prefs.remove('clientLogo'); - await prefs.remove('clientName'); - await prefs.remove('empAllowed_modules'); - await prefs.remove('empClientBranchId'); - await prefs.remove('empClientId'); - await prefs.remove('empEmail'); - await prefs.remove('empHrId'); - await prefs.remove('empPrimaryId'); - await prefs.remove('enrollmentAllowed_modules'); - await prefs.remove('enrollmentClient_id'); - await prefs.remove('enrollmentEmpClientBranchId'); - await prefs.remove('enrollmentEmpPrimaryId'); - await prefs.remove('enrollmentHrId'); - await prefs.remove('token'); - Navigator.pushNamed(context, 'branchSelection'); + await tokenService.clearBranchSession(); + + if (!mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'branchSelection', + (route) => false, + ); }, ), const SizedBox(width: 8), diff --git a/lib/customAppBar/side_bar.dart b/lib/customAppBar/side_bar.dart new file mode 100644 index 0000000..3a4d987 --- /dev/null +++ b/lib/customAppBar/side_bar.dart @@ -0,0 +1,306 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/svg_service.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; + +class NhanceSideBar extends StatefulWidget { + const NhanceSideBar({super.key}); + + @override + State createState() => _NhanceSideBarState(); +} + +class _NhanceSideBarState extends State { + String? activeRoute; + late ApiService apiService; + // bool isLoading = true; // Add a loading state + // bool hideInactiveStatus = true; + final tokenService = TokenStorageService(); + // dynamic enrollmentModules = []; + // dynamic postModules = []; + + List> sideMenuItems = []; + + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + _buildSideMenu(); + // _checkTokens(); + } + + + + + Future _buildSideMenu() async { + final enrollmentRaw = + await tokenService.readValue('enrollmentAllowed_modules'); // "[1]" + final postRaw = + await tokenService.readValue('empAllowed_modules'); // "[2,3,4]" + + print('enrollmentRaw $enrollmentRaw'); + print('postRaw $postRaw'); + + // ✅ Decode safely + final List enrollmentModules = + enrollmentRaw != null && enrollmentRaw.isNotEmpty + ? List.from(jsonDecode(enrollmentRaw)) + : []; + + final List postModules = + postRaw != null && postRaw.isNotEmpty + ? List.from(jsonDecode(postRaw)) + : []; + + print('enrollmentModules $enrollmentModules'); + print('postModules $postModules'); + + final List> items = []; + + // ✅ POLICIES (1 OR 2) + if (enrollmentModules.contains(1) || postModules.contains(2)) { + items.add({ + 'route': 'policies', + 'label': 'Policies', + 'icon': 'policies', + }); + } + + // CD + if (postModules.contains(3)) { + items.add({ + 'route': 'CdPoliciesList', + 'label': 'CD', + 'icon': 'cd', + }); + } + + // CLAIMS + if (postModules.contains(4)) { + items.add({ + 'route': 'ClaimsPolicies', + 'label': 'Claims', + 'icon': 'claims', + }); + } + + setState(() { + sideMenuItems = items; + }); + } + + + Future logout(BuildContext context) async { + // final prefs = await SharedPreferences.getInstance(); + // final String? hrtoken = prefs.getString('_postToken'); + // final String? token = prefs.getString('enrollToken'); + // prefs.clear(); + print('LocalStorage Cleared'); + apiService.logout(); + // Navigator.pushNamed(context, 'hrLogin'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + activeRoute = ModalRoute.of(context)?.settings.name; + } + + void _navigate(String routeName) { + if (activeRoute == routeName) return; + + setState(() { + activeRoute = routeName; + }); + + Navigator.pushReplacementNamed(context, routeName); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 70, + color: const Color(0xFF009E9E), + child: Column( + children: [ + // const SizedBox(height: 12), + + // + // _SideItem( + // // icon: Icons.policy, + // icon: SvgPicture.string( + // SvgService.getSvg('policies'), + // width: 35, + // height: 35, + // colorFilter: const ColorFilter.mode( + // Colors.white, + // BlendMode.srcIn, + // ), + // ), + // label: "Policies", + // isActive: activeRoute == 'oldPolicy', // or correct policy route + // onTap: () => _navigate('oldPolicy'), + // ), + // + // _SideItem( + // // icon: Icons.credit_card, + // icon: SvgPicture.string( + // SvgService.getSvg('cd'), + // width: 35, + // height: 35, + // colorFilter: const ColorFilter.mode( + // Colors.white, + // BlendMode.srcIn, + // ), + // ), + // label: "CD", + // isActive: activeRoute == 'cdTransactionDetails', + // onTap: () => _navigate('cdTransactionDetails'), + // ), + // + // _SideItem( + // // icon: Icons.assignment, + // icon: SvgPicture.string( + // SvgService.getSvg('claims'), + // width: 35, + // height: 35, + // colorFilter: const ColorFilter.mode( + // Colors.white, + // BlendMode.srcIn, + // ), + // ), + // label: "Claims", + // isActive: activeRoute == 'claims', // claims tab inside dashboard + // onTap: () => _navigate('claims'), + // ), + // + // const Spacer(), + // + // _SideItem( + // // icon: Icons.logout, + // icon: SvgPicture.string( + // SvgService.getSvg('logout'), + // width: 35, + // height: 35, + // colorFilter: const ColorFilter.mode( + // Colors.white, + // BlendMode.srcIn, + // ), + // ), + // label: "Logout", + // onTap: () { + // logout(context); + // }, + // ), + + ...sideMenuItems.map((item) { + return _SideItem( + icon: SvgPicture.string( + SvgService.getSvg(item['icon']), + width: 35, + height: 35, + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), + ), + label: item['label'], + isActive: activeRoute == item['route'], + onTap: () => _navigate(item['route']), + ); + }).toList(), + + if(activeRoute == 'CdPoliciesList' || activeRoute == 'ClaimsPolicies' || activeRoute == 'policies' || activeRoute == 'hrDashboard') + _SideItem( + // icon: Icons.dashboard, + icon: SvgPicture.string( + SvgService.getSvg('dashboard'), + width: 35, + height: 35, + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), + ), + label: "Insights", + isActive: activeRoute == 'hrDashboard', + onTap: () => _navigate('hrDashboard'), + ), + + const Spacer(), + + _SideItem( + icon: SvgPicture.string( + SvgService.getSvg('logout'), + width: 35, + height: 35, + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), + ), + label: "Logout", + onTap: () => logout(context), + ), + + // const SizedBox(height: 10), + ], + ), + ); + } +} + + +class _SideItem extends StatelessWidget { + // final IconData icon; + final Widget icon; // 👈 changed + final String label; + final bool isActive; + final VoidCallback? onTap; + + const _SideItem({ + required this.icon, + required this.label, + this.isActive = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: 6), + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: isActive + ? const Color(0xFF065D61) // ✅ ACTIVE like your screenshot + : Colors.transparent, + ), + child: Column( + children: [ + // 👇 SVG or Icon widget + icon, + // Icon(icon, color: Colors.white, size: 22), + const SizedBox(height: 6), + Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + } +} + + diff --git a/lib/customAppBar/toastHelper.dart b/lib/customAppBar/toastHelper.dart index 926cac8..05e1cea 100755 --- a/lib/customAppBar/toastHelper.dart +++ b/lib/customAppBar/toastHelper.dart @@ -9,7 +9,12 @@ class ToastHelper { type: ToastificationType.success, 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/customAppBar/top_app_bar.dart b/lib/customAppBar/top_app_bar.dart new file mode 100644 index 0000000..cb57038 --- /dev/null +++ b/lib/customAppBar/top_app_bar.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import '../service/token_storage_service.dart'; + +class NhanceTopBar extends StatefulWidget implements PreferredSizeWidget { + const NhanceTopBar({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(64); + + @override + State createState() => _NhanceTopBarState(); +} + +class _NhanceTopBarState extends State { + final tokenStorage = TokenStorageService(); + + List> branches = []; + Map? selectedBranch; + + @override + void initState() { + super.initState(); + _loadBranches(); + } + + void _loadBranches() { + branches = tokenStorage.getCombinedBranches(); + selectedBranch = tokenStorage.getSelectedBranch(); + setState(() {}); + } + + Future _onBranchSelected(Map branch) async { + final tokenStorage = TokenStorageService(); + + await tokenStorage.resetSessionAndSwitchBranch(branch); + + if (!mounted) return; + + // 🔄 Refresh CURRENT PAGE only + final route = ModalRoute.of(context)?.settings.name ?? 'hrDashboard'; + + Navigator.pushReplacementNamed(context, route); + } + + + @override + Widget build(BuildContext context) { + return AppBar( + automaticallyImplyLeading: false, + backgroundColor: const Color(0xFFBFEFEF), + elevation: 0, + title: Row( + children: [ + Image.asset('assets/nhance_client_logo.png', height: 36), + const Spacer(), + + if (selectedBranch != null) + _BranchPopup( + clientName: selectedBranch!['client_name'], + branchName: selectedBranch!['branch_name'], + branches: branches, + onSelected: _onBranchSelected, + ), + ], + ), + ); + } +} +class _BranchPopup extends StatelessWidget { + final String clientName; + final String branchName; + final List> branches; + final Function(Map) onSelected; + + const _BranchPopup({ + required this.clientName, + required this.branchName, + required this.branches, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return PopupMenuButton>( + tooltip: '', + offset: const Offset(0, 48), + onSelected: onSelected, + + itemBuilder: (context) { + return branches.map((branch) { + return PopupMenuItem>( + value: branch, + child: Row( + children: [ + Expanded( + child: Text( + branch['client_name'], + style: const TextStyle(fontSize: 13), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 10), + const Icon(Icons.location_on, size: 14, color: Colors.grey), + const SizedBox(width: 4), + Text( + branch['branch_name'], + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + ); + }).toList(); + }, + + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // CLIENT NAME + Text( + clientName, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + + const SizedBox(width: 8), + + // 📍 BRANCH NAME (SELECTED) + Row( + children: [ + const Icon( + Icons.location_on, + size: 14, + color: Colors.grey, + ), + const SizedBox(width: 4), + Text( + branchName, + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + + const SizedBox(width: 6), + + // DROPDOWN ARROW + const Icon( + Icons.keyboard_arrow_down, + size: 20, + color: Colors.orange, + ), + ], + ), + ), + ); + } +} diff --git a/lib/email_verify.dart b/lib/email_verify.dart index 16b1661..3f469cb 100755 --- a/lib/email_verify.dart +++ b/lib/email_verify.dart @@ -129,7 +129,7 @@ class _MyEmailVerifyState extends State { if (mainStatus != 'success' && postStatus != 'success') { ToastHelper.showErrorToast( context, - postEnrollment1['message'] ?? 'User not found', + postEnrollment1['message'], ); return; } @@ -430,33 +430,7 @@ class _MyEmailVerifyState extends State { @override Widget build(BuildContext context) { - // Retrieve the passed mobile number value - // final String mobileNumber = - // ModalRoute.of(context)!.settings.arguments as String; - Size _size = MediaQuery.of(context).size; - EdgeInsets marginInsets = EdgeInsets.zero; - if (Responsive.isDesktop(context)) { - marginInsets = const EdgeInsets.only( - left: 0, - right: 0, - bottom: 0, - top: 0, - ); - } else if (Responsive.isMobile(context)) { - marginInsets = const EdgeInsets.only( - left: 25, // Example value for mobile - right: 25, // Example value for mobile - bottom: 0, // Example value for mobile - top: 0, // Example value for mobile - ); - } else if (Responsive.isTablet(context)) { - marginInsets = const EdgeInsets.only( - left: 25, // Example value for mobile - right: 25, // Example value for mobile - bottom: 0, // Example value for mobile - top: 0, // Example value for mobile - ); - } + final Size _size = MediaQuery.of(context).size; final defaultPinTheme = PinTheme( width: 56, height: 56, @@ -482,495 +456,263 @@ class _MyEmailVerifyState extends State { ), ); - return WillPopScope( - onWillPop: () async { - Navigator.pushReplacementNamed(context, 'login'); - return false; - }, - child: Scaffold( - body: SingleChildScrollView( - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, - child: Container( - height: _size.height, - color: Colors.white, - child: Stack( - children: [ - // Visibility( - // visible: _size.width <= 1100, - // child: ClipRRect( - // borderRadius: BorderRadius.only( - // bottomLeft: Radius.circular(30), - // bottomRight: Radius.circular(30), - // ), - // child: Container( - // height: _size.height / 3, - // width: double.infinity, - // color: Color(0xFFFFFCE5), - // child: Stack( - // children: [ - // Column( - // children: [ - // SizedBox( - // height: _size.height / - // 6.4), // Adjust the spacing between the rows - // Row( - // mainAxisAlignment: MainAxisAlignment - // .center, // Align to the center - // children: [ - // Expanded( - // flex: Responsive.isDesktop(context) - // ? 10 - // : 12, - // child: Align( - // alignment: Responsive.isDesktop(context) - // ? Alignment.centerLeft - // : Alignment.bottomCenter, - // child: Image.asset( - // 'assets/nhance_app_logo.png', - // width: 150, - // height: 150, - // ), - // ), - // ), - // if (!Responsive.isMobile(context) && - // !Responsive.isTablet(context)) - // Expanded( - // flex: 2, - // child: MouseRegion( - // cursor: SystemMouseCursors.click, - // child: GestureDetector( - // onTap: () { - // // Add your navigation logic here - // // For example, you can use Navigator.push to navigate to another page - // Navigator.pushNamed( - // context, 'hrLogin'); - // }, - // child: Row( - // mainAxisAlignment: MainAxisAlignment - // .end, // Align to the end (right) - // children: [ - // Text( - // 'HR Login', - // style: GoogleFonts.poppins( - // color: Color( - // 0xFF000000), // Text color - // // Add other text styles as needed - // ), - // ), - // SizedBox(width: 5), - // Icon( - // Icons - // .east, // Icon for customer login - // color: Colors - // .black, // Adjust color as needed - // ), - // ], - // ), - // ), - // ), - // ), - // ], - // ), - // ], - // ), - // ], - // ), - // ), - // ), - // ), - Container( - margin: marginInsets, - alignment: Alignment.bottomCenter, - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - children: [ - Row( - children: [ - if (_size.width > 1100) - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints constraints) { - if (constraints.maxWidth > 600) { - return Image.asset( - 'assets/hrLogin.jpg', - height: _size.height, - fit: BoxFit.cover, - ); - } else { - return SizedBox(); - } - }, - ), - ), - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: Container( - margin: _size.width > 1100 - ? EdgeInsets.only(left: 20, right: 20) - : EdgeInsets.only(left: 0, right: 0), - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - if (!Responsive.isMobile(context) && - !Responsive.isTablet(context)) - Row( - children: [ - Expanded( - flex: 10, - child: Align( - alignment: Responsive - .isDesktop(context) - ? Alignment.center - : Alignment - .bottomCenter, // Align to the start - child: _size.width <= 1100 - ? Image.asset( - 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 150, - ) - : _size.width > 1100 - ? Image.asset( - 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 150, - ) - : Image.asset( - 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 150, - ), - )), - ], - ), - SizedBox(height: 10), - // SizedBox( - // height: Responsive.isDesktop(context) - // ? _size.height * 0.1 - // : 10, - // ), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - "Welcome to Nhance", - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - SizedBox(height: 10), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: - "Please enter the one-time usage code sent to your ", - style: TextStyle( - fontSize: 12, - height: 1.5, - color: Color(0xFF000000)), - children: [ - TextSpan( - text: widget.type == 'mobile' ? 'mobile number ${widget.value}' : 'Email Id ${widget.value}', - ), - TextSpan( - text: widget.type == 'mobile' ? ' (Change Mobile)' : ' (Change Email)', - style: TextStyle( - fontSize: 12, - color: Color( - 0xFFE26728)), // Change color as desired - recognizer: - TapGestureRecognizer() - ..onTap = () { - // Navigate to the page where the user can change the phone number - Navigator.pushNamed( - context, - 'hrLogin'); - }, - ), - ], - ), - ), - ), - SizedBox(height: 15), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: Pinput( - length: 6, - // defaultPinTheme: defaultPinTheme, - // focusedPinTheme: focusedPinTheme, - // submittedPinTheme: submittedPinTheme, - showCursor: true, - controller: _otpController, - ), - ), - SizedBox(height: 10), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: Row( - mainAxisAlignment: MainAxisAlignment - .end, // Align text to the right - children: [ - _isTimerRunning - ? Text( - "Resend OTP in $_secondsRemaining seconds", - style: - GoogleFonts.poppins( - color: - Colors.black), - ) - : InkWell( - onTap: () { - _resendOTP(); - }, - child: Text( - "Resend OTP", - style: - GoogleFonts.poppins( - color: Colors - .blue), - ), - ), - ], - ), - ), - SizedBox(height: 10), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: SizedBox( - width: double.infinity, - height: 45, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: - Color(0xFF00989E), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10), - ), - ), - onPressed: () { - if (_formKey.currentState! - .validate()) { - _formKey.currentState! - .save(); // Save form fields before calling verifyOTP - verifyOTP( - _otpController.text); - } - }, - child: Text( - "Submit", - style: GoogleFonts.poppins( - color: Color(0xFFFFFFFF)), - ), - ), - ), - ), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Column( - // children: [ - // SizedBox(height: 20), - // Text( - // "Benefits of Login", - // style: - // GoogleFonts.poppins( - // fontSize: 20, - // fontWeight: - // FontWeight.bold, - // ), - // ), - // SizedBox(height: 15), - // ], - // )) - // : SizedBox(), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.center, - // children: [ - // Expanded( - // flex: 6, - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: 8), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment - // .center, - // children: [ - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // decoration: - // BoxDecoration( - // border: - // Border( - // right: - // BorderSide( - // width: 1, - // color: Colors - // .black, - // ), - // ), - // ), - // child: Column( - // children: [ - // Icon( - // Icons - // .policy, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: - // 10), - // Text( - // "View Policy", - // style: GoogleFonts - // .poppins()), - // ], - // ), - // ), - // ), - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // child: Column( - // children: [ - // Icon( - // Icons - // .edit, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: - // 10), - // Text( - // "Manage Claims", - // style: GoogleFonts - // .poppins()), - // ], - // ), - // ), - // ), - // ], - // ), - // ), - // ), - // ], - // ), - // ) - // : SizedBox( - // height: Responsive.isDesktop( - // context) - // ? _size.height * 0.1 - // : _size.height * 0.2, - // ), - SizedBox( - height: Responsive.isDesktop(context) - ? _size.height * 0.3 - : _size.height * 0.2, - ), - // SizedBox( - // height: _size.height * 0.1, - // ), - Container( - alignment: Alignment.bottomCenter, - padding: - EdgeInsets.symmetric(vertical: 8), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: - 'By continuing, you agree with our ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - children: [ - TextSpan( - text: 'privacy policy ', - style: GoogleFonts.poppins( - color: Color(0xFF00989E), - fontSize: 9, - ), - ), - TextSpan( - text: 'and ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - ), - TextSpan( - text: 'terms of use', - style: GoogleFonts.poppins( - color: Color(0xFF00989E), - fontSize: 9, - ), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ], - ), - ], + return Scaffold( + body: Container( + width: double.infinity, + height: _size.height, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + Color(0xFF00B6AC), + Color(0xFF83E0DE), + Color(0xFF01B4A8), + ], + ), + ), + + child: Center( + child: Container( + width: double.infinity, // ✅ fixed web width + height: _size.height, // ✅ fixed web height (IMPORTANT) + margin: const EdgeInsets.all(60), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(40)), + ), + child: Row( + children: [ + // ================= LEFT IMAGE ================= + Expanded( + flex: 5, + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(40), + bottomLeft: Radius.circular(40), + ), + child: Image.asset( + 'assets/hrLogin.png', + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, ), ), ), - ), - ], - )), - ))); + + // ================= RIGHT FORM ================= + Expanded( + flex: 7, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/Nhance-Logo-Final 1.png', + width: 300, + height: 100, + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + "Welcome to Nhance", + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + "Please enter the one-time usage code sent to your ", + style: TextStyle( + fontSize: 12, + height: 1.5, + color: Color(0xFF000000)), + children: [ + TextSpan( + text: widget.type == 'mobile' ? 'mobile number ${widget.value}' : 'Email Id ${widget.value}', + ), + TextSpan( + text: widget.type == 'mobile' ? ' (Change Mobile)' : ' (Change Email)', + style: TextStyle( + fontSize: 12, + color: Color( + 0xFFE26728)), // Change color as desired + recognizer: + TapGestureRecognizer() + ..onTap = () { + // Navigate to the page where the user can change the phone number + Navigator.pushNamed( + context, + 'hrLogin'); + }, + ), + ], + ), + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Pinput( + length: 6, + // defaultPinTheme: defaultPinTheme, + // focusedPinTheme: focusedPinTheme, + // submittedPinTheme: submittedPinTheme, + showCursor: true, + controller: _otpController, + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment + .end, // Align text to the right + children: [ + _isTimerRunning + ? Text( + "Resend OTP in $_secondsRemaining seconds", + style: + GoogleFonts.poppins( + color: + Colors.black), + ) + : InkWell( + onTap: () { + _resendOTP(); + }, + child: Text( + "Resend OTP", + style: + GoogleFonts.poppins( + color: Colors + .blue), + ), + ), + ], + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 45, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + Color(0xFF00989E), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(10), + ), + ), + onPressed: () { + if (_formKey.currentState! + .validate()) { + _formKey.currentState! + .save(); // Save form fields before calling verifyOTP + verifyOTP( + _otpController.text); + } + }, + child: Text( + "Submit", + style: GoogleFonts.poppins( + color: Color(0xFFFFFFFF)), + ), + ), + ), + ), + SizedBox(height: 15), + Container( + alignment: Alignment.bottomCenter, + padding: + EdgeInsets.symmetric(vertical: 8), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + 'By continuing, you agree with our ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + children: [ + TextSpan( + text: 'privacy policy ', + style: GoogleFonts.poppins( + color: Color(0xFF00989E), + fontSize: 9, + ), + ), + TextSpan( + text: 'and ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + ), + TextSpan( + text: 'terms of use', + style: GoogleFonts.poppins( + color: Color(0xFF00989E), + fontSize: 9, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ) + + ) + ); } } diff --git a/lib/excel_verification.dart b/lib/excel_verification.dart deleted file mode 100755 index 4efad7a..0000000 --- a/lib/excel_verification.dart +++ /dev/null @@ -1,1434 +0,0 @@ -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:jwt_decode/jwt_decode.dart'; -import 'package:nhancepolicy/customAppBar/customAppBar.dart'; -import 'dart:convert'; -import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:nhancepolicy/customAppBar/toastHelper.dart'; -import 'package:nhancepolicy/hrPolicyDetails.dart'; -import 'package:nhancepolicy/service/api_service.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:universal_html/html.dart' as html; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:excel/excel.dart'; -import 'dart:io'; -import 'package:intl/intl.dart'; -import 'package:csv/csv.dart'; - -import 'package:spreadsheet_decoder/spreadsheet_decoder.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'config/environment.dart'; -import 'customAppBar/customFooter.dart'; - -class excelVerify extends StatefulWidget { - final String ClientId; - final String policyTypeId; - final String ClientPoliyId; - final String clientBranchId; - final String Token; - final String TokenType; - final String cardType; - final String cardPolicyNo; - final String cardInsurer_name; - final String cardPolicy_name; - final String cardPolicy_ExpDate; - const excelVerify( - {Key? key, - required this.ClientId, - required this.policyTypeId, - required this.ClientPoliyId, - required this.clientBranchId, - required this.Token, - required this.TokenType, - required this.cardType, - required this.cardPolicyNo, - required this.cardInsurer_name, - required this.cardPolicy_name, - required this.cardPolicy_ExpDate}) - : super(key: key); - - @override - State createState() => _excelVerifyState(); -} - -class _excelVerifyState extends State { - Uint8List? fileBytes; - Uint8List? fileBytes2; - late String _token; - dynamic getPolicyNo; - bool _isLoading = false; - dynamic getPolicyNameDetails; - dynamic clintID; - String? fileName; - int _currentStep = 0; // Step index tracker - List dataPolicy = []; - dynamic validationArray = []; - dynamic missingColumnErrorMsg = 0; - dynamic columnIndexMismatchCount = 0; - dynamic columnMissingCount = 0; - List> extractedData = []; - dynamic argumentsData; - List> originalData = []; // Original data source - List> filteredData = []; // Filtered data source - List> tableData = []; // Filtered data source - - List> nonExcelFilteredData = []; - dynamic invalidRelationships = 0; - dynamic dobAgeCheckCount = 0; - dynamic empRefId; - List excelHeader = []; - List>> excelData = []; - late int excelValidationStaus = 1; - bool isSuccess = false; - String successContent = ''; - bool isLoading = false; - late ApiService apiService; - - @override - void initState() { - super.initState(); - apiService = ApiService(context); - _loadToken(); - } - - @override - void dispose() { - super.dispose(); - html.window.localStorage.remove('fileBytes'); - } - - Future _loadToken() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - // final token = prefs.getString('hrtoken'); - final token = widget.Token; - if (token != null && token.isNotEmpty) { - setState(() { - _token = token; - }); - Map? decodedToken = Jwt.parseJwt(token); - print('decodedToken $decodedToken'); - } else { - // Token is empty or null, handle accordingly (e.g., navigate to login screen) - // For now, let's navigate to the login screen - ToastHelper.showErrorToast(context, 'Session Out'); - Navigator.pushReplacementNamed(context, 'hrLogin'); - } - } - - // Future getPolicyDetails() async { - // setState(() { - // clientPolicyId = argumentsData['client_policy_id']; - // clientId = argumentsData['client_id']; - // policyType = argumentsData['type']; - // policy_name = argumentsData['policy_name']; - // }); - // } - - // Future _uploadFile1(importPolicyName) async { - // FilePickerResult? result = await FilePicker.platform.pickFiles( - // type: FileType.custom, - // allowedExtensions: ['xlsx', 'xls', 'csv'], - // ); - // - // if (result != null) { - // PlatformFile file = result.files.first; - // Uint8List fileBytes = file.bytes!; - // // Use the fileBytes as needed - // print('File name: ${file.name}'); - // print('File size: ${file.size}'); - // print('File bytes: $fileBytes'); - // _processExcelData(fileBytes); - // } else { - // // User canceled the picker - // } - // } - - void _uploadFile(importPolicyName) async { - print('Test'); - if (kIsWeb) { - print('kIsWeb'); - final input = html.FileUploadInputElement(); - input.accept = '.xlsx,.xls,.csv'; - input.click(); - input.onChange.listen((event) async { - final file = input.files!.first; - final reader = html.FileReader(); - reader.readAsArrayBuffer(file); - await reader.onLoadEnd.first; // Wait for the file to be loaded - if (reader.readyState == html.FileReader.DONE) { - Uint8List? fileBytes = reader.result as Uint8List?; - if (fileBytes != null) { - setState(() { - fileName = file.name; - }); - // Save fileBytes to local storage - final jsonString = json.encode(fileBytes); - html.window.localStorage['fileBytes'] = jsonString; - print('File Name: $fileName'); - print('File Bytes: $fileBytes'); - sendExcelFIleTOAPI(fileBytes, fileName); - // Call the function to process Excel data here - // _processExcelData(fileBytes, fileName); - } - } - }); - } else { - // Handle non-web platforms here (e.g., show an error message) - print('File upload is only supported on web platforms.'); - } - } - - void _processExcelData(Uint8List fileBytes, fileName) { - List> dataArray; - if (fileName.endsWith('.xlsx')) { - dataArray = decodeExcelData(fileBytes); - } else if (fileName.endsWith('.xls')) { - dataArray = decodeXLSData(fileBytes); - } else if (fileName.endsWith('.csv')) { - print('csv'); - dataArray = decodeCSVData(fileBytes); - } else { - throw UnsupportedError('Unsupported file format: $fileName'); - } - - print('_processExcelData'); - // Decode the Excel file and extract relevant data - // Assuming dataArray is your array containing Excel data - // List> dataArray = decodeExcelData(fileBytes); - print(dataArray); - // print(dataArray[0].toString()); - // Extract Name, Age, and City from the array - print(dataArray[0].length); - - if (dataArray[0].length == 11) { - for (int i = 0; i < dataArray.length; i++) { - Map dataMap = { - "Sno": dataArray[i][0].value, - "Emp_Code": dataArray[i][1].value, - "Name": dataArray[i][2].value, - "DOJ": dataArray[i][3].value, - "Gender": dataArray[i][4].value, - "Relation": dataArray[i][5].value, - "DOB": dataArray[i][6].value, - "Mail": dataArray[i][7].value, - "Mobile": dataArray[i][8].value, - "SI": dataArray[i][9].value, - "Grade": dataArray[i][10].value, - }; - if (i == 0) { - // print(dataMap); - validationArray.add(dataMap); - } else { - // print(dataMap); - extractedData.add(dataMap); - } - } - // Do something with extracted data (e.g., display in UI) - originalData = extractedData; - filteredData = List.from(originalData); - print('filteredData'); - print(filteredData); - print(validationArray); - - validationArray[0].forEach((key, value) { - print(key); - print(value); - if (key.toString().trim().toLowerCase() != - value.toString().trim().toLowerCase()) { - // If key and value are not equal, increment mismatch count - columnIndexMismatchCount++; - print('columnIndexMismatchCount: $value'); - } - if (value.toString() == 'null') { - // If value is null, increment missing count - columnMissingCount++; - print('Value: $value'); - } - }); - - print('columnIndexMismatchCount: $columnIndexMismatchCount'); - print('columnMissingCount: $columnMissingCount'); - - nonExcelFilteredData = filteredData.where((item) { - final relation = item['Relation']; - return relation != null && - relation.toString().trim().toLowerCase() != 'self'; - }).toList(); - print('nonExcelFilteredData'); - - print(nonExcelFilteredData); - print(nonExcelFilteredData.length); - if (argumentsData['type'] == 'GPA') { - if (nonExcelFilteredData.length > 0) { - print('nonSelf'); - invalidRelationships = nonExcelFilteredData.length; - } else { - print('Self'); - invalidRelationships = nonExcelFilteredData.length; - } - print('invalidRelationships: $invalidRelationships'); - } else { - invalidRelationships = 0; - } - - int invalidDobCount = countInvalidDobs(filteredData); - - print('Number of invalid DOBs: $invalidDobCount'); - - dobAgeCheckCount = invalidDobCount; - } else { - print('Some Column is Missing'); - var columnMissingCount = 11 - dataArray[0].length; - missingColumnErrorMsg = columnMissingCount; - print(missingColumnErrorMsg); - } - } - - // bool checkAllSelf(List> dataList) { - // // Check if any value of 'Relation' key is not 'Self' - // bool allSelf = dataList.every((data) => data['Relation'] == 'Self'); - // // If all values are 'Self', return true; otherwise, return false - // return allSelf; - // } - - // Placeholder function for decoding Excel data - List> decodeExcelData(Uint8List fileBytes) { - print('decodeExcelData'); - - // Create an Excel instance from the fileBytes - final excel = Excel.decodeBytes(fileBytes); - print('decodeExcelData'); - print(excel); - - // Assuming there's only one sheet in the Excel file - final sheet = excel.tables.keys.first; - final table = excel.tables[sheet]!; - - // Convert Excel table to a List> - // Convert Excel table to a List> - List> dataArray = []; - for (int rowIdx = 0; rowIdx < table.rows.length; rowIdx++) { - List rowData = []; - for (int colIdx = 0; colIdx < table.rows[rowIdx].length; colIdx++) { - var value = table.rows[rowIdx][colIdx]?.value; - rowData.add(Data(value, rowIdx, colIdx, sheet)); - } - dataArray.add(rowData); - } - return dataArray; - } - - List> decodeXLSData(Uint8List fileBytes) { - final Excel excelData = Excel.decodeBytes(fileBytes); - final sheet = excelData.tables.keys.first; - final table = excelData.tables[sheet]!; - List> dataArray = []; - for (int rowIdx = 0; rowIdx < table.rows.length; rowIdx++) { - List rowData = []; - for (int colIdx = 0; colIdx < table.rows[rowIdx].length; colIdx++) { - var value = table.rows[rowIdx][colIdx]?.value; - rowData.add(Data(value, rowIdx, colIdx, sheet)); - } - dataArray.add(rowData); - } - return dataArray; - } - - List> decodeCSVData(Uint8List fileBytes) { - String csvString = utf8.decode(fileBytes); - List> csvData = const CsvToListConverter().convert(csvString); - List> dataArray = []; - - // Skip the header row (if it exists) and start from index 1 - for (int i = 1; i < csvData.length; i++) { - List rowData = []; - for (int j = 0; j < csvData[i].length; j++) { - // Assuming the CSV data is of type String - rowData.add(Data(csvData[i][j].toString(), i, j, 'Sheet1')); - } - dataArray.add(rowData); - } - return dataArray; - } - - int countInvalidDobs(List> data) { - int invalidCount = 0; - - for (var entry in data) { - DateTime dob; - - // if (entry['DOB'] is String) { - dob = DateTime.parse(entry['DOB'].toString()); - // } else if (entry['DOB'] is DateTime) { - // dob = entry['DOB']; - // } else { - // // Invalid DOB format, skip this entry - // continue; - // } - print(entry['Relation']); - if ((entry['Relation'].toString() == 'Son' || - entry['Relation'].toString() == 'Daughter')) { - if (DateTime.now().difference(dob).inDays > 25 * 365) { - print('child $dob'); - invalidCount++; - } - } else if ((entry['Relation'] != 'Son' && - entry['Relation'] != 'Daughter')) { - if (DateTime.now().difference(dob).inDays < 18 * 365) { - print('others $dob'); - invalidCount++; - } - } - } - - return invalidCount; - } - - void _dragAndDropFile(html.File file) async { - print('file'); - print(file); - // Prepare form data - final formData = html.FormData(); - formData.appendBlob('file', file); - - // Send formData to API endpoint - final response = await html.HttpRequest.request( - 'your_api_endpoint_here', - method: 'POST', - sendData: formData, - ); - - // Handle response as needed - print(response.responseText); - } - - void _retrieveAndUploadFile() { - final jsonString = html.window.localStorage['fileBytes']; - if (jsonString != null) { - final decodedBytes = json.decode(jsonString); - if (decodedBytes is List) { - setState(() { - fileName = fileName ?? - 'Retrieved File'; // Provide a default name if fileName is null - }); - sendExcelFIleTOAPI( - Uint8List.fromList(decodedBytes.cast()), - fileName!, - ); - } - } - } - - Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { - // Future.delayed(Duration(seconds: 3), () { - // setState(() { - isLoading = true; - // }); - // }); - print('submit'); - print(fileBytes); - if (fileBytes == null) { - print('return'); - return; // No file selected - } else { - print('else'); - // // Prepare form data - // final formData = html.FormData(); - // formData.appendBlob('file', html.Blob([fileBytes]), fileName); - - // URL of the API where you want to send the file - final apiUrl = Environment.apiUrl + 'employeeUpload'; - print('else'); - // Create a multipart request - final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); - print('else'); - // Attach the file to the request - // Set authorization token in headers - request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; - request.headers['Authorization'] = 'Bearer $_token'; - // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, - // filename: fileName)); - print('Filename: $fileName'); - request.files.add(http.MultipartFile.fromBytes( - 'file', - fileBytes, - filename: fileName ?? 'default_filename.xlsx', - )); - print('clintID: $clintID'); - request.fields['client_id'] = widget.ClientId; - // if (policyFirstPart == 'GPA') { - request.fields['policy_id'] = widget.ClientPoliyId; - request.fields['client_branch_id'] = widget.clientBranchId; - // } else { - // request.fields['policy_id'] = '3'; - // } - print('request : $request'); - // Send the request - final response = await request.send(); - print('else'); - // Read response stream as a string - final responseString = await response.stream.bytesToString(); - print('else'); - // Check the status code of the response - if (response.statusCode == 200) { - isLoading = false; - Map data = json.decode(responseString); - if (data['status'] == 'success') { - ToastHelper.showSuccessToast(context, data['message']); - print('Table'); - - setState(() { - isSuccess = true; - successContent = data['message']; - excelValidationStaus = 0; - // excelHeader = List.from(data['data']['excel_header']); - // print('excelHeader : $excelHeader'); - // excelData = (data['data']['excel_data'] as List) - // .map>>((row) => row - // .map>( - // (cell) => Map.from(cell)) - // .toList()) - // .toList(); - // print('excelHeader : $excelHeader'); - }); - handleImportAction(); - } else { - setState(() { - isLoading = false; - }); - ToastHelper.showErrorToast2(context,"",data['message']); - // ToastHelper.showErrorToast(context, data['message']); - print('Table'); - - setState(() { - isSuccess = false; - excelValidationStaus = 1; - excelHeader = List.from(data['data']['excel_header']); - print('excelHeader : $excelHeader'); - excelData = (data['data']['excel_data'] as List) - .map>>((row) => row - .map>( - (cell) => Map.from(cell)) - .toList()) - .toList(); - print('excelData : $excelData'); - print('excelHeader : $excelHeader'); - }); - } - } else { - setState(() { - isLoading = false; - }); - // ToastHelper.showSuccessToast( - // context, 'Failed to upload file: ${response.reasonPhrase}'); - ToastHelper.showErrorToast(context, 'Something went wrong'); - print('Failed to upload file: ${response.reasonPhrase}'); - } - } - } - - Future handleImportAction() async { - print('handleImportAction'); - - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final postId = prefs.getString('empHrId'); - final preId = prefs.getString('enrollmentEmpPrimaryId'); - var activity = "import_enrollempdata"; - - dynamic response; - - print('postId - $postId'); - print('preId - $preId'); - print('activity - $activity'); - - try { - print('10'); - - response = await apiService.getImportLogHrActivity( - postId!, preId!, widget.Token, activity); - - if (response['status'] == 'success') { - print('Request success'); - } else { - // ToastHelper.showWarningToast( - // context, 'Request failed with status: ${response.statusCode}'); - print('Request failed with status: ${response['code']}'); - } - } catch (e) { - print('Exception occurred: $e'); - } - } - - void search(String query) { - setState(() { - if (query.isEmpty) { - // If search query is empty, show all data - filteredData = List.from(originalData); - } else { - // Filter the data based on the search query - filteredData = originalData.where((item) { - // Implement your filter logic here, for example: - return item['Emp_Code'].toLowerCase().contains(query.toLowerCase()); - }).toList(); - } - }); - } - - downloadSampleFile() { - final anchor = html.AnchorElement(href: 'assets/assets/Template_File.xlsx'); - anchor.download = 'Template_File.xlsx'; // Set the filename - anchor.click(); // Trigger a click on the anchor element - } - - // Future downloadSampleFile() async { - // - // final response = await apiService.getSampleFileDownload(widget.Token); - // print('check 1'); - // if (response['status'] == 'success') { - // final url = response['data']; - // _launchURL(url); - // } else { - // ToastHelper.showErrorToast(context, '⚠️ Unknown response format'); - // print('⚠️ Unknown response format'); - // } - // } - // - // Future _launchURL(String url) async { - // print('url $url'); - // try { - // final Uri uri = Uri.parse(url); - // await launchUrl(uri, mode: LaunchMode.externalApplication); - // } catch (e) { - // print('Could not launch URL: $e'); - // } - // } - - resetErrorCount() { - setState(() { - fileBytes = null; - fileName = null; - _currentStep = 0; - excelHeader.clear(); - excelData.clear(); - - // extractedData = []; - // originalData = []; - // filteredData = []; - // validationArray = []; - // nonExcelFilteredData = []; - // print('filteredData'); - // print(filteredData); - // print(validationArray); - // - // missingColumnErrorMsg = 0; - // print(missingColumnErrorMsg); - // columnMissingCount = 0; - // print(columnMissingCount); - // columnIndexMismatchCount = 0; - // print(columnIndexMismatchCount); - // invalidRelationships = 0; - // dobAgeCheckCount = 0; - // - // html.window.localStorage.remove('fileBytes'); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar(), - body: Stack(children: [ - SafeArea( - child: Theme( - data: ThemeData( - canvasColor: Color(0xFFF4F7FE), - colorScheme: Theme.of(context).colorScheme.copyWith( - primary: Color(0xFF00999E), - background: Colors.red, - secondary: Color(0xFF00999E), - ), - ), - child: Container( - padding: const EdgeInsets.all(20), - color: Color(0xFFEFF3F6), - child: Card( - elevation: 0, - color: Colors.white, - child: Column( - children: [ - Expanded( - child: Stepper( - type: StepperType.horizontal, - currentStep: _currentStep, - controlsBuilder: - (BuildContext context, ControlsDetails controls) { - return Container( - alignment: Alignment.bottomCenter, - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - ElevatedButton( - onPressed: () { - html.window.localStorage - .remove('fileBytes'); - fileBytes = null; - fileName = null; - _currentStep = 0; - excelHeader.clear(); - excelData.clear(); - // missingColumnErrorMsg = 0; - // columnIndexMismatchCount = 0; - // columnMissingCount = 0; - // invalidRelationships = 0; - // dobAgeCheckCount = 0; - // nonExcelFilteredData = []; - // Navigator.pushNamed( - // context, 'hrPolicyDetails', - // arguments: argumentsData); - - Navigator.pop(context); - }, - child: Text( - 'Close', - style: - TextStyle(color: Color(0xFFE26728)), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - side: BorderSide( - color: Color(0xFFE26728)), - ), - ), - ), - // SizedBox(width: 10), - // if (fileName != null) - // ElevatedButton( - // onPressed: () { - // // Reset localStorage and fileBytes - // setState(() { - // html.window.localStorage - // .remove('fileBytes'); - // fileBytes = null; - // fileName = null; - // _currentStep = 0; - // }); - // }, - // child: Text( - // 'Reselect File', - // style: TextStyle(color: Color(0xFFE26728)), - // ), - // style: ElevatedButton.styleFrom( - // backgroundColor: Colors.white, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(5), - // side: - // BorderSide(color: Color(0xFFE26728)), - // ), - // ), - // ), - Spacer(), - SizedBox( - width: - 10), // This pushes the buttons to the right - if (_currentStep != 0) - ElevatedButton( - onPressed: () { - controls.onStepCancel!(); - }, - child: Text( - 'Previous', - style: - TextStyle(color: Color(0xFFE26728)), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(5), - side: BorderSide( - color: Color(0xFFE26728)), - ), - ), - ), - SizedBox(width: 10), - ElevatedButton( - child: const Text( - 'NEXT', - style: TextStyle(color: Colors.white), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - ), - // Disable button if excelValidationStatus is 1 - // Use ternary operator to conditionally set onPressed to null - onPressed: (_currentStep == 0 && - fileName == null) || - (_currentStep == 1 && - excelValidationStaus == 1) - ? null - : () { - if (_currentStep == 0) { - // For Step 0: Enable continue if fileName is not null - if (fileName != null) { - controls.onStepContinue!(); - } - } else if (_currentStep == 1) { - // For Step 1: Check excelValidationStatus - if (excelValidationStaus == 0) { - // Navigate to hrPolicyDetails page - // Navigator.pushNamed( - // context, 'hrPolicyDetails', - // arguments: argumentsData); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - hrPolicyDetails( - ClientId: widget - .ClientId, - policyTypeId: widget - .policyTypeId, // <-- from map - 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, - - // Token: widget.Token, - // ClientId: widget.ClientId, - // ClientPolicyId : widget.ClientPoliyId, - // PolicyName: widget.cardPolicy_name, - // PolicyNo: widget.cardPolicyNo, - // ClientBranchId: widget.HrId, - // PolicyType: widget.cardType, - ), - ), - ); - } - // If excelValidationStatus is 1, button will be disabled (do nothing) - } - }, - ), - SizedBox(width: 10), - // if (_currentStep == 2) - // ElevatedButton( - // onPressed: () { - // _retrieveAndUploadFile(); - // }, - // child: Text( - // 'Submit', - // style: TextStyle(color: Colors.white), - // ), - // style: ElevatedButton.styleFrom( - // backgroundColor: Color(0xFFE26728), - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(5), - // ), - // ), - // ), - ], - ), - ); - }, - onStepContinue: _currentStep == 2 - ? null - : () { - if (_currentStep == 0) { - // For the first step, allow continue if fileName is not null - if (fileName != null) { - setState(() { - _currentStep += 1; - }); - } - } else if (_currentStep == 1) { - // For the second step, allow continue if both errors are 0 - if (missingColumnErrorMsg == 0 && - columnIndexMismatchCount == 0 && - invalidRelationships == 0 && - dobAgeCheckCount == 0) { - setState(() { - _currentStep += 1; - }); - } - } - // setState(() { - // // Increment the current step when the user clicks continue - // if (_currentStep < 2) { - // _currentStep += 1; - // } - // }); - }, - onStepCancel: () { - setState(() { - // Decrement the current step when the user clicks cancel - if (_currentStep > 0) { - _currentStep -= 1; - } - }); - }, - steps: [ - Step( - title: Container( - child: Text( - 'Import Excel', - style: GoogleFonts.poppins( - color: Color(0xFF00999E), - ), - ), - ), - content: Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - flex: 12, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Expanded( - child: Text( - '${widget.cardPolicy_name} - (${widget.cardType})', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight - .w600, // Adjust the font size as needed - color: Color( - 0xFF181818), // Adjust the text color as needed - ), - ), - ) - ], - ), - SizedBox(height: 20), - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - margin: EdgeInsets.symmetric( - horizontal: 150), - alignment: Alignment.center, - color: Colors.grey[200], - height: - 250, // Adjust height as needed - child: DragTarget( - onAccept: (html.File - droppedFile) { - setState(() { - fileName = - droppedFile.name; - }); - _dragAndDropFile( - droppedFile); - }, - builder: - (BuildContext context, - List - candidateData, - List - rejectedData) { - return Container( - alignment: - Alignment.center, - child: Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - // if (fileName == null) - // ElevatedButton( - // onPressed: () => {}, - // child: Icon( - // Icons.download, - // color: Colors - // .grey), // You can replace the Icon with your custom button child - // style: - // ElevatedButton - // .styleFrom( - // shape: - // CircleBorder(), // Makes the button round - // padding: - // EdgeInsets.all( - // 18), // Change the button color as needed - // ), - // ), - // SizedBox(height: 20), - // if (fileName == null) - // Text( - // 'Drag and drop file to import', - // style: TextStyle( - // fontSize: 16, - // fontWeight: - // FontWeight - // .w600), - // ), // Add spacing between texts - // SizedBox(height: 20), - fileName != null - ? Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .upload_file, // Choose the appropriate icon - size: - 35, - color: Color( - 0xFFE26728), // Adjust the size as needed - ), - SizedBox( - height: - 15), // Add some space between the icon and text - Text( - '$fileName', - style: TextStyle( - fontSize: - 16), - ), - SizedBox( - height: - 25), // Add some space between the icon and text - MouseRegion( - cursor: - SystemMouseCursors.click, // Set cursor to pointer on hover - child: - GestureDetector( - onTap: - () { - resetErrorCount(); - }, - child: - const Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.delete_forever, // Choose the remove icon - size: 20, // Adjust the size as needed - color: Colors.red, // Set the color of the remove icon - ), - SizedBox(width: 1), // Add some space between the icon and text - Text( - 'Remove', - style: TextStyle(fontSize: 13, color: Color(0xFF727272)), - ), - ], - ), - ), - ) - ], - ) - : ElevatedButton( - onPressed: () => _uploadFile('Policy Name'), - child: Text( - 'Select File', - style: TextStyle( - color: - Colors.white), - ), - style: ElevatedButton - .styleFrom( - backgroundColor: - Color( - 0xFFE26728), - ), - ), - SizedBox( - height: 20), - if (fileName == - null) - Text( - 'Supported Files : XLSX') - // Add more Text widgets for additional lines of text - ], - ), - ); - }, - ), - ), - ), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisAlignment: - MainAxisAlignment - .start, - children: [ - Text( - 'Ensure that the import file is in the correct format by comparing it with our template file.', - textAlign: - TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: - FontWeight.w400, - )), - MouseRegion( - cursor: SystemMouseCursors - .click, - child: GestureDetector( - onTap: () { - downloadSampleFile(); - }, - child: Text( - 'Template File', - style: TextStyle( - fontSize: 15, - color: Color( - 0xFF00999E), // Add underline decoration - ), - ), - ), - ) - ])) - ], - ) - ], - ), - ), - ], - ), - ), - isActive: _currentStep == 0, - ), - - Step( - title: Text( - 'Excel Validation', - style: TextStyle(color: Color(0xFF00999E)), - ), - content: isSuccess - ? Center( - child: Text( - successContent, - style: TextStyle( - fontSize: 20, color: Colors.green), - ), - ) - : excelHeader.isNotEmpty && - excelData.isNotEmpty - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: DataTable( - columns: excelHeader - .map((header) => DataColumn( - label: Text(header))) - .toList(), - rows: excelData - .map((row) => DataRow( - cells: row - .map( - (cell) => - DataCell( - cell.containsKey( - 'error') - ? Row( - children: [ - Text(cell['value'] ?? ''), - SizedBox(width: 5), - IconButton( - icon: Icon( - Icons.error_outline, - color: Colors.red, - size: 16, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - elevation: 0, - backgroundColor: Colors.white, - title: Text('Error Details'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: (cell['error'] as List).map((e) => Text(e)).toList(), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text('OK'), - ), - ], - ); - }, - ); - }, - ), - ], - ) - : Text(cell['value'] ?? - ''), - )) - .toList(), - )) - .toList(), - ), - ), - ) - : Center( - child: CircularProgressIndicator()), - isActive: _currentStep == 1, - ), - // Step( - // title: Text( - // 'Preview', - // style: TextStyle(color: Color(0xFFE26728)), - // ), - // content: Column( - // children: [ - // Row( - // children: [ - // Expanded( - // flex: 10, - // child: Container( - // alignment: Alignment.centerLeft, - // child: Container( - // width: - // 350, // Set your desired width here - // height: - // 40, // Set your desired height here - // decoration: BoxDecoration( - // boxShadow: [ - // BoxShadow( - // color: Color.fromRGBO( - // 255, - // 255, - // 255, - // 0.5), // Shadow color with opacity - // offset: Offset(5, - // 5), // Shadow position (horizontal, vertical) - // blurRadius: 10, // Blur radius - // spreadRadius: - // 0, // Spread radius - // ), - // ], - // borderRadius: - // BorderRadius.circular(5), - // ), - // child: TextField( - // textAlignVertical: TextAlignVertical - // .center, // Center the text vertically - // decoration: InputDecoration( - // hintText: 'Search', - // suffixIcon: Icon(Icons.search), - // contentPadding: EdgeInsets.all( - // 10), // Adjust the horizontal padding - // border: OutlineInputBorder( - // borderSide: BorderSide( - // color: Color( - // 0xFFf5f5f7)), // Set border color to gray - // ), - // ), - // onChanged: - // search, // Call the search method on text change - // ), - // )), - // ), - // ], - // ), - // SizedBox(height: 20), - // Row( - // children: [ - // Expanded( - // child: SingleChildScrollView( - // child: _buildDataTable(), - // ), - // ) - // ], - // ), - // ], - // ), - // isActive: _currentStep == 2, - // ), - ], - ), - ), - // SizedBox(height: 20), - // if (_currentStep == 2) - // Align( - // alignment: Alignment.bottomRight, - // child: ElevatedButton( - // onPressed: () { - // sendExcelFIleTOAPI(); - // }, - // child: Text( - // 'Complete', - // style: TextStyle(color: Colors.white), - // ), - // style: ElevatedButton.styleFrom( - // backgroundColor: Color(0xFFE26728), - // ), - // ), - // ), - // SizedBox(height: 20), - ], - ), - ), - ), - ), - ), - if (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 - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double.infinity, // Make the footer full width - child: CustomFooter(), - ), - ), - ])); - } - - Widget _buildDataTable() { - if (filteredData.isEmpty) { - SizedBox(height: 25); - return Text('No available data'); - } else { - return Card( - elevation: 0, // Set elevation to 0 for no shadow - child: PaginatedDataTable( - rowsPerPage: 25, // Adjust rows per page as needed - columns: [ - DataColumn(label: Text('S.No')), - DataColumn(label: Text('Employee ID')), - DataColumn(label: Text('Name')), - DataColumn(label: Text('Date of Joining')), - DataColumn(label: Text('Gender')), - DataColumn(label: Text('Relationship')), - DataColumn(label: Text('Date of Birth')), - DataColumn(label: Text('Mail')), - DataColumn(label: Text('Mobile No')), - DataColumn(label: Text('SI')), - DataColumn(label: Text('Grade')), - ], - source: _DependenceDataSource0(filteredData), - ), - ); - } - } - - Widget _buildRowWithIcon(IconData iconData, String text, - {Color iconColor = Colors.black}) { - return Row( - children: [ - Icon( - iconData, - color: iconColor, // Set color based on the parameter - ), - SizedBox(width: 8), // Add some space between icon and text - Text(text), // Text widget - ], - ); - } -} - -class Data { - final dynamic value; - final int row; - final int column; - final String sheet; - - Data(this.value, this.row, this.column, this.sheet); -} - -class _DependenceDataSource0 extends DataTableSource { - final List> _data; - _DependenceDataSource0(this._data); - - @override - DataRow getRow(int index) { - final row = _data[index]; - - String dob = row['DOB'] != null ? formatDate(row['DOB']) : 'N/A'; - String doj = row['DOJ'] != null ? formatDate(row['DOJ']) : 'N/A'; - - return DataRow(cells: [ - DataCell(Text(row['Sno'].toString())), - DataCell(Text(row['Emp_Code'].toString())), - DataCell(Text(row['Name'].toString())), - DataCell(Text(doj)), - DataCell(Text(row['Gender']?.toString() ?? 'N/A')), - DataCell(Text(row['Relation']?.toString() ?? 'N/A')), - DataCell(Text(dob)), - DataCell(Text(row['Mail']?.toString() ?? 'N/A')), - DataCell(Text(row['Mobile']?.toString() ?? 'N/A')), - DataCell(Text(row['SI']?.toString() ?? 'N/A')), - DataCell(Text(row['Grade']?.toString() ?? 'N/A')), - ]); - } - - @override - bool get isRowCountApproximate => false; - - @override - int get rowCount => _data.length; - - @override - int get selectedRowCount => 0; - - String formatDate(dynamic dateValue) { - if (dateValue is String) { - // If the date is already in string format - DateTime dateTime = DateTime.parse(dateValue); - return DateFormat('dd-MM-yyyy').format(dateTime); - } else if (dateValue is DateCellValue) { - // If dateValue is an instance of DateCellValue - return DateFormat('dd-MM-yyyy') - .format(DateTime.parse(dateValue.toString())); - } else { - // Handle other cases or null values - return 'N/A'; - } - } -} diff --git a/lib/hrDashboard.dart b/lib/hrDashboard.dart deleted file mode 100755 index 8ce04ec..0000000 --- a/lib/hrDashboard.dart +++ /dev/null @@ -1,979 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:jwt_decode/jwt_decode.dart'; -import 'package:nhancepolicy/responsive.dart'; -import 'package:nhancepolicy/service/api_service.dart'; -import 'package:nhancepolicy/service/hrDashboardTabs/activePolicies.dart'; -import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart'; -import 'package:nhancepolicy/service/hrDashboardTabs/claims.dart'; -import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart'; -import 'package:nhancepolicy/service/token_storage_service.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:http/http.dart' as http; -import 'config/environment.dart'; -import 'customAppBar/customAppBar.dart'; -import 'customAppBar/customFooter.dart'; -import 'customAppBar/toastHelper.dart'; -import 'package:universal_html/html.dart' as html; -import 'package:intl/intl.dart'; - -class hrDashboard extends StatefulWidget { - final int selectedIndex; - late final int isHrcode; - final String empCodeFromHrPolicy; - - hrDashboard({ - Key? key, - required this.selectedIndex, - required this.isHrcode, - required this.empCodeFromHrPolicy, - }) : super(key: key); - - @override - State createState() => _hrDashboardState(); -} - -class _hrDashboardState extends State - with SingleTickerProviderStateMixin { - late ApiService apiService; - late TabController _tabController; - bool isLoading = false; - int isHrcode = 0; - - String? enrollToken = ''; - String? _postToken = ''; - late String _token; - dynamic getPolicyNo; - dynamic branchName; - // bool _isLoading = false; - dynamic getPolicyNameDetails; - dynamic enrollmentClient_id; - dynamic policy_name; - dynamic getCardArrays = []; - List empAllowed_modules = []; - - List visibleTabs = []; - List tabViews = []; - dynamic clientName; - dynamic clientLogo; - dynamic empClientBranchId; - dynamic empHrId; - List enrollmentAllowed_modules = []; - List> tabData = []; - dynamic enrollmentEmpClientBranchId; - dynamic enrollmentHrId; - dynamic empClientId; - String empCodeFromHrPolcy = ''; - - final List cardColors = [ - Color(0xFFFFE3D9), - Color(0xFFFFD9EE), - Color(0xFFDBFFDE), - Color(0xFFE4DFFF), - ]; - ScrollController _scrollController = ScrollController(); - int selectedIndex = 0; - final tokenService = TokenStorageService(); - - @override - void initState() { - super.initState(); - apiService = ApiService(context); // Initialize ApiService here - checkToken(); - empCodeFromHrPolcy = widget.empCodeFromHrPolicy; - - // _tabController = TabController(length: 4, vsync: this); - // _tabController.addListener(() { - // setState(() { - // selectedIndex = _tabController.index; - // }); - // }); - // Future.delayed(Duration(seconds: 3), () { - // setState(() { - // isLoading = false; - // }); - // }); - - } - - @override - void dispose() { - super.dispose(); - _tabController.dispose(); - } - - checkToken() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - // enrollToken = prefs.getString('pre_enrollment_data'); - // _postToken = prefs.getString('post_enrollment_data'); - - // Get the current token - final token = await tokenService.getCurrentToken(); - print('token - $token'); - - print('token check in'); - if((token != null && token!.isNotEmpty)){ - print('token check done'); - _loadToken(); - } else { - print('token check reject'); - ToastHelper.showErrorToast(context, 'Session Out'); - Navigator.pushReplacementNamed(context, 'hrLogin'); - } - } - - Future _loadToken() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - enrollToken = await tokenService.getCurrentToken(); - _postToken = await tokenService.getCurrentToken(); - print(enrollToken); - print(_postToken); - branchName = prefs.getString('branchName') ?? ''; - - if (enrollToken != null && enrollToken!.isNotEmpty) { - // Map? decodedToken = Jwt.parseJwt(enrollToken!); - // enrollmentClient_id = decodedToken['client_id'].toString(); - enrollmentClient_id = prefs.getString('enrollmentClient_id'); - enrollmentEmpClientBranchId = - prefs.getString('enrollmentEmpClientBranchId'); - enrollmentHrId = prefs.getString('enrollmentHrId'); - - // print("Pre decodedToken - $decodedToken"); - print("Pre enrollmentClient_id - $enrollmentClient_id"); - print("Pre enrollmentEmpClientBranchId - $enrollmentEmpClientBranchId"); - print("Pre enrollmentHrId - $enrollmentHrId"); - - String? modulesString = prefs.getString('enrollmentAllowed_modules'); - if (modulesString != null) { - enrollmentAllowed_modules = jsonDecode(modulesString); - print( - "enrollmentAllowed_modules - $enrollmentAllowed_modules"); // [2, 3, 4] - - if (enrollmentAllowed_modules.contains(1)) { - tabData.add({ - 'icon': Icons.grid_view, - 'label': 'Pre Enrollment', - }); - print("getCardArrays.length = ${getCardArrays.length}"); - - // tabViews.add( - // PreEnrollment( - // enrollmentClientId: enrollmentClient_id, - // enrollmentClientBranchId: enrollmentEmpClientBranchId, - // enrollmentHrId: enrollmentHrId, - // enrollToken: enrollToken, - // ), - // ); - } - } - - if (prefs.containsKey('clientLogo') && prefs.containsKey('clientName')) { - clientLogo = prefs.getString('clientLogo'); - clientName = prefs.getString('clientName'); - } else { - empClientId = prefs.getString('empClientId'); - empClientBranchId = prefs.getString('empClientBranchId'); - getClientLogoAndDetails(empClientId,empClientBranchId, - enrollmentEmpClientBranchId, enrollmentClient_id, enrollToken); - } - getCashDepositDetails(enrollmentEmpClientBranchId, enrollmentClient_id, - enrollmentHrId, enrollToken); - } - - if (_postToken != null && _postToken!.isNotEmpty) { - // Map? postdecodedToken = Jwt.parseJwt(_postToken!); - - // empClientId = postdecodedToken['client_id'].toString(); - empClientId = prefs.getString('empClientId'); - empClientBranchId = prefs.getString('empClientBranchId'); - empHrId = prefs.getString('empHrId'); - - print('post empHrId- $empHrId'); - // print('postdecodedToken- $postdecodedToken'); - print('post empClientId- $empClientId'); - print('post empClientBranchId- $empClientBranchId'); - - print('allowed_modules'); - String? modulesString = prefs.getString('empAllowed_modules'); - - // String? enrollmodulesString = - // prefs.getString('enrollmentAllowed_modules'); - if (modulesString != null) { - empAllowed_modules = jsonDecode(modulesString); - // enrollmentAllowed_modules = jsonDecode(enrollmodulesString!); - print("empAllowed_modules - $empAllowed_modules"); // [2, 3, 4] - // print( - // "enrollmentAllowed_modules - $enrollmentAllowed_modules"); // [2, 3, 4] - - if (empAllowed_modules.contains(2)) { - print("visibleTabs.length2"); - print(visibleTabs.length); - - tabData.add({ - 'icon': Icons.verified_user, - 'label': 'Active Policies', - }); - // tabViews.add( - // ActivePolicies( - // empClientId: empClientId, - // empClientBranchId: empClientBranchId, - // empHrId: empHrId, - // postToken: _postToken, - // ), - // ); - } - if (empAllowed_modules.contains(3)) { - print("visibleTabs.length3"); - print(visibleTabs.length); - - tabData.add({ - 'icon': Icons.desktop_windows, - 'label': 'CD', - }); - - // tabViews.add(CdPolicies( - // empClientId: empClientId, - // empClientBranchId: empClientBranchId, - // empHrId: empHrId, - // postToken: _postToken, - // )); - } - print('333'); - if (empAllowed_modules.contains(4)) { - tabData.add({ - 'icon': Icons.receipt_long, - 'label': 'Claims', // Label Name Integrated with code (dont change) - }); - // tabViews.add(ClaimsPolicies( - // empClientId: empClientId, - // empClientBranchId: empClientBranchId, - // empHrId: empHrId, - // postToken: _postToken, - // isHrcode: widget.isHrcode == 1 ? 1 : 0, - // empCodeHrPolicy: empCodeFromHrPolcy - // // empCodeHrPolicy: widget.empCodeFromHrPolicy - // )); - } - } - // getCashDepositDetails( - // empClientBranchId, empClientId, empHrId, _postToken); - if (prefs.containsKey('clientLogo') && prefs.containsKey('clientName')) { - clientLogo = prefs.getString('clientLogo'); - clientName = prefs.getString('clientName'); - } else { - empClientId = prefs.getString('empClientId'); - empClientBranchId = prefs.getString('empClientBranchId'); - getClientLogoAndDetails(empClientId,empClientBranchId, - enrollmentEmpClientBranchId, enrollmentClient_id, _postToken); - } - print("getCashDepositDetails"); - } - - _tabController = TabController(length: tabData.length, vsync: this); - - print('selectedIndex ${widget.selectedIndex}'); - // var isHrcode; - print('111'); - if (widget.selectedIndex == 3) { - print("Process1"); - print("ProcessTAb - $tabData"); - - final claimsIndex = tabData.indexWhere((tab) => tab['label'] == 'Claims'); - print('Claims tab index: $claimsIndex'); - _tabController.index = claimsIndex; - // _tabController.index = 3; - setState(() { - selectedIndex = claimsIndex; - // selectedIndex = 3; - isHrcode = 1; - empCodeFromHrPolcy = widget.empCodeFromHrPolicy; - }); - print("tabIndexConti11- $selectedIndex - ${_tabController.index}"); - // print("tabIndexCode11- $isHrcode - ${widget.isHrempcode}"); - } - - print('222'); - _tabController.addListener(() { - int newIndex = _tabController.index; - - setState(() { - selectedIndex = newIndex; - if (newIndex != 3 && empCodeFromHrPolcy.isNotEmpty) { - empCodeFromHrPolcy = ''; // ✅ clear only once - } - - // selectedIndex = _tabController.index; - // empCodeFromHrPolcy = ''; - - print("Process2"); - print("tabIndexConti- $selectedIndex - ${_tabController.index}"); - - // print("tabIndexCode11- $isHrcode "); - }); - }); - - setState(() {}); - - isLoading = false; - } - - List getTabViews() { - return tabData.map((tab) { - final label = tab['label']; - - switch (label) { - case 'Claims': - return ClaimsPolicies( - empClientId: empClientId, - empClientBranchId: empClientBranchId, - empHrId: empHrId, - postToken: _postToken!, - isHrcode: widget.isHrcode == 1 ? 1 : 0, - empCodeHrPolicy: empCodeFromHrPolcy, - ); - case 'CD': - return CdPolicies( - empClientId: empClientId, - empClientBranchId: empClientBranchId, - empHrId: empHrId, - postToken: _postToken!, - ); - case 'Active Policies': - return ActivePolicies( - empClientId: empClientId, - empClientBranchId: empClientBranchId, - empHrId: empHrId, - postToken: _postToken!, - ); - case 'Pre Enrollment': - return PreEnrollment( - enrollmentClientId: enrollmentClient_id, - enrollmentClientBranchId: enrollmentEmpClientBranchId, - enrollmentHrId: enrollmentHrId, - enrollToken: enrollToken!, - ); - default: - return Center(child: Text('Unknown tab')); - } - }).toList(); - } - - Future getClientLogoAndDetails(post_client_id, post_client_branch_id, - pre_client_branch_id, pre_client_id, token) async { - var url = Uri.parse(Environment.apiUrl + - 'getClientDetails?post_client_id=$post_client_id&post_branch_id=$post_client_branch_id&pre_client_id=$pre_client_id&pre_branch_id=$pre_client_branch_id'); - try { - var response = await http.get( - url, - headers: { - 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', - 'Authorization': - 'Bearer $token', // Add token to the Authorization header - }, - ); - if (response.statusCode == 200) { - print('response.statusCode == 200'); - Map data = json.decode(response.body); - print(data); - - if (data.containsKey('data')) { - dynamic clientDetails = data['data']; - final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setString('clientLogo', clientDetails['client']['client_logo']); - prefs.setString('clientName', clientDetails['client']['client_name']); - setState(() { - // dynamic clientDetails = data['data']; - // print(clientDetails); - clientName = clientDetails['client']['client_name']; - print(clientName); - clientLogo = clientDetails['client']['client_logo']; - print(clientLogo); - }); - } else { - // Handle other status messages if needed - // ToastHelper.showErrorToast( - // context, 'API request failed with status: ${data['status']}'); - print('API request failed with status: ${data['status']}'); - } - } else { - // Handle other status codes - // ToastHelper.showErrorToast( - // context, 'Request failed with status: ${response.statusCode}'); - print('Request failed with status: ${response.statusCode}'); - } - } catch (e) { - // Handle exceptions - print('Exception occurred: $e'); - } - } - - Future getCashDepositDetails( - clintBranchId, clintID, hr_id, token) async { - print('IN'); - print("clintBranchId -$clintBranchId"); - print("clintID -$clintID"); - print("hr_id -$hr_id"); - print("token -$token"); - - isLoading = true; - // setState(() { - // _isLoading = true; - // }); - try { - if (clintBranchId == null || clintID == null) { - return; - } - final response = await apiService.getCashDepositDetailsToApi( - clintID!, clintBranchId!, hr_id, token); - - // final response = await apiService.getCashDepositDetailsToApi( - // clintID!, clintBranchId!, hr_id, token); - print('IN1'); - if (response['status'] == 'success') { - isLoading = false; - setState(() { - print('response'); - print(response['data']); - getCardArrays = List>.from(response['data']); - print('getCardArrays'); - print(getCardArrays); - }); - - print('IN2'); - } else { - print('API request failed with status'); - } - } catch (e) { - print('Exception occurred: $e'); - } - } - - Future downloadExcel(clintId, insurerId, insurerName) async { - // API endpoint to download the Excel file - - // Send GET request to the API - var url = Uri.parse(Environment.apiUrl + - 'exportCashDepositData?client_id=$clintId&insurer_id=$insurerId'); - - var response = await http.get( - url, - headers: { - 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', - 'Authorization': 'Bearer $_token', - }, - ); - - // Check if the request was successful (status code 200) - // Check if the request was successful (status code 200) - // Check if the request was successful (status code 200) - if (response.statusCode == 200) { - // Create a blob from the response body - final blob = html.Blob([response.bodyBytes]); - - // Generate a download URL for the blob - final url = html.Url.createObjectUrlFromBlob(blob); - - // Create a link element to trigger the download - final anchor = html.AnchorElement(href: url) - ..setAttribute('download', '$insurerName.xlsx') - ..click(); - - // Revoke the download URL to free up resources - html.Url.revokeObjectUrl(url); - } else { - // Handle error - print('Failed to download Excel file: ${response.statusCode}'); - } - } - - final List> policies = List.generate(10, (index) { - return { - "policyNo": index % 2 == 0 - ? "GMC - S70000/48/2025/401" - : "GMC - 4016/X0/351234479/00/000", - "insurer": "ICICI Lombard General Insurance Company Limited", - "draft": "1234", - "enrolled": "4", - "total": "1234", - }; - }); - - 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; - } - - @override - Widget build(BuildContext context) { - if (getCardArrays == []) { - return PopScope( - canPop: false, // ✅ Prevents automatic back navigation - onPopInvoked: (didPop) async { - // This is triggered when browser back button is pressed - bool logout = await _showLogoutDialog(); - - if (logout) { - // ✅ Clear storage & logout - // Example: - // await storage.clear(); - final prefs = await SharedPreferences.getInstance(); - prefs.clear(); - print('LocalStorage Cleared'); - Navigator.pushNamed(context, 'hrLogin'); - } - }, - child: Scaffold( - appBar: CustomAppBar(), - body: SingleChildScrollView( - child: Container( - color: Color(0xFFEFF3F6), - child: Column( - children: [ - Center( - child: Text('No Data Available'), - ) - ], - ), - ), - ))); - } else { - int numCards = getCardArrays.length; - int numExpanded = numCards < 4 ? numCards : 4; - double cardHeight = MediaQuery.of(context).size.height / 4.5; - double policyHeight = MediaQuery.of(context).size.height / 4.5; - double cardWidth = MediaQuery.of(context).size.width / 6; - - print("visibleTabAll - $visibleTabs"); - print("tabViewsAll - $tabViews"); - return PopScope( - canPop: false, // ✅ Prevents automatic back navigation - onPopInvoked: (didPop) async { - // This is triggered when browser back button is pressed - bool logout = await _showLogoutDialog(); - - if (logout) { - // ✅ Clear storage & logout - // Example: - // await storage.clear(); - final prefs = await SharedPreferences.getInstance(); - prefs.clear(); - print('LocalStorage Cleared'); - Navigator.pushNamed(context, 'hrLogin'); - } - }, - child: Scaffold( - appBar: CustomAppBar(), - body: Stack(children: [ - Container( - padding: const EdgeInsets.only( - top: 20, bottom: 40, left: 40, right: 40), - - // padding: const EdgeInsets.only( - // top: 20, bottom: 20, left: 50, right: 50), - color: const Color(0xFFEFF3F6), - child: Column( - children: [ - Container( - color: Colors.white, - width: double.infinity, - // height: 75, - height: MediaQuery.of(context).size.height * 0.12, - - padding: Responsive.isDesktop(context) - ? const EdgeInsets.only( - top: 10, bottom: 10, left: 20, right: 20) - : EdgeInsets.only( - top: 10, - bottom: 10, - left: 10, - right: 10), // Add padding to the container - child: Row( - children: [ - Expanded( - flex: 6, - child: Container( - width: 150, - height: 80, - alignment: Alignment.centerLeft, - child: Image.network( - Uri.encodeFull(clientLogo ?? ''), - width: 200, - height: 200, - fit: BoxFit.contain, - frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedOpacity( - opacity: frame == null ? 0 : 1, - duration: const Duration(milliseconds: 500), - curve: Curves.easeOut, - child: child, - ); - }, - loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) { - return child; - } - return Center( - child: SizedBox( - width: 30, - height: 30, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.grey), - value: loadingProgress.expectedTotalBytes != null - ? loadingProgress.cumulativeBytesLoaded / - (loadingProgress.expectedTotalBytes ?? 1) - : null, - ), - ), - ); - }, - errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { - return Text(''); - // Image.asset( - // 'assets/Solid_gray.png', - // width: 80, - // height: 60, - // fit: BoxFit.cover, - // ); - }, - ), - ), - ), - Expanded( - flex: 9, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, // right aligned - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // ⭐ Client Name - Text( - clientName ?? '', - textAlign: TextAlign.right, - style: GoogleFonts.poppins( - fontSize: Responsive.isDesktop(context) ? 20 : 18, - fontWeight: FontWeight.w600, - ), - ), - - const SizedBox(height: 20), - - // ⭐ Branch Name (new line) - Text( - 'Branch: ${branchName}' ?? '', - textAlign: TextAlign.right, - style: GoogleFonts.poppins( - fontSize: Responsive.isDesktop(context) ? 16 : 14, - fontWeight: FontWeight.w400, - color: Colors.grey[700], - ), - ), - ], - ), - ) - - ], - ), - ), - - SizedBox(height: 8), - - tabData.isNotEmpty - ? Container( - // padding: const EdgeInsets.all(5), - // height: MediaQuery.of(context).size.height * 0.08, - decoration: BoxDecoration( - color: Color( - 0xFFC4E3E6), // 🔹 Background behind the TabBar - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.transparent), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.15), - blurRadius: 4, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // mainAxisSize: MainAxisSize.min, - children: [ - TabBar( - controller: _tabController, - isScrollable: true, - labelPadding: EdgeInsets.zero, - dividerColor: Colors.transparent, - indicatorColor: Colors.transparent, - indicatorPadding: EdgeInsets.zero, - labelColor: Colors.white, - unselectedLabelColor: const Color(0xFF828282), - // tabs: visibleTabs, - tabs: List.generate(tabData.length, (index) { - final data = tabData[index]; - return CustomTab( - icon: data['icon'], - label: data['label'], - isSelected: selectedIndex == index, - ); - }), - ), - ], - ), - ) - : Container( - height: MediaQuery.of(context).size.height * 0.6, - // color: Colors.white, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - 'assets/claimsData.png', // Replace 'default_image.png' with your default image asset path - width: 300, - height: 300, - fit: BoxFit.cover, - ), - const Text("No Data Available"), - ], - ), - ), - - SizedBox(height: 16), - // Expanded TabBarView - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: TabBarView( - controller: _tabController, - children: getTabViews(), - // children: tabViews, - ), - ), - ), - ], - ), - ), - if (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 - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double.infinity, // Make the footer full width - child: CustomFooter(), - ), - ), - ])) - ); - - - } - } - - Widget buildPolicyCard(Map policy) { - print("buildPolicyCard - $policy"); - return SizedBox( - height: 50, - child: Card( - elevation: 3, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - policy['type'] ?? '', - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 14, - ), - ), - const SizedBox(height: 4), - Text( - policy['policy_name'] ?? '', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildCountBox( - policy['membersCountOfDraft'].toString(), "Draft"), - _buildCountBox( - policy['membersCountOfEnrolled'].toString(), "Enrolled"), - _buildCountBox( - policy['totalMembersCount'].toString(), "Total"), - ], - ), - ], - ), - ), - ), - ); - } - - Widget _buildCountBox(String count, String label) { - return Column( - children: [ - Container( - width: 50, - height: 40, - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0xFFDFF1F3), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - count, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - const SizedBox(height: 6), - Text(label, - style: const TextStyle(fontSize: 12, color: Colors.black87)), - ], - ); - } -} - -class CustomTab extends StatelessWidget { - final IconData icon; - final String label; - final bool isSelected; - - const CustomTab({ - required this.icon, - required this.label, - required this.isSelected, - }); - - @override - Widget build(BuildContext context) { - print("isSelected - $isSelected"); - return Tab( - child: Container( - // margin: const EdgeInsets.symmetric(horizontal: 6), - margin: const EdgeInsets.only(right: 6), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - decoration: BoxDecoration( - color: isSelected ? const Color(0xFF00999E) : Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - if (isSelected) - BoxShadow( - color: Colors.black.withOpacity(0.2), - blurRadius: 4, - offset: const Offset(0, 2), - ) - else - BoxShadow( - color: Colors.grey.withOpacity(0.2), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], - ), - child: Row( - // mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, - size: 18, - color: isSelected ? Colors.white : const Color(0xFF828282)), - const SizedBox(width: 6), - Text( - label, - style: TextStyle( - fontWeight: FontWeight.w600, - color: isSelected ? Colors.white : const Color(0xFF828282), - ), - ), - ], - ), - ), - ); - } -} - -class PolicyCard extends StatelessWidget { - final Map policy; - - const PolicyCard({required this.policy}); - - @override - Widget build(BuildContext context) { - return Card( - elevation: 4, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(policy["policyNo"], - style: const TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(policy["insurer"], - style: const TextStyle(fontSize: 11, color: Colors.grey)), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildStatusBox("Draft", policy["draft"]), - _buildStatusBox("Enrolled", policy["enrolled"]), - _buildStatusBox("Total", policy["total"]), - ], - ) - ], - ), - ), - ); - } - - Widget _buildStatusBox(String label, String value) { - return Column( - children: [ - Text(value, - style: const TextStyle( - fontWeight: FontWeight.bold, color: Colors.teal)), - const SizedBox(height: 2), - Text(label, style: const TextStyle(fontSize: 11, color: Colors.grey)), - ], - ); - } -} \ No newline at end of file diff --git a/lib/hrHome.dart b/lib/hrHome.dart index ad6de60..8465391 100755 --- a/lib/hrHome.dart +++ b/lib/hrHome.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:nhancepolicy/customAppBar/customAppBar.dart'; -import 'package:nhancepolicy/excel_verification.dart'; +import 'package:nhancepolicy/presentation/preFileUpload.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'dart:convert'; @@ -353,7 +353,7 @@ class _MyHrHomeState extends State with TickerProviderStateMixin { // argumentDetails = 'Dependent-AddOn'; // } - Navigator.pushNamed(context, 'excelVerify', arguments: argumentDetails); + Navigator.pushNamed(context, 'preFileUpload', arguments: argumentDetails); return; if (kIsWeb) { final input = html.FileUploadInputElement(); diff --git a/lib/hrLogin.dart b/lib/hrLogin.dart index 8662031..80bfa97 100755 --- a/lib/hrLogin.dart +++ b/lib/hrLogin.dart @@ -9,9 +9,9 @@ import 'package:nhancepolicy/hrVerify.dart'; import 'dart:convert'; import 'dart:io'; import 'package:nhancepolicy/responsive.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_animated_button/flutter_animated_button.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'config/environment.dart'; import 'email_verify.dart'; @@ -39,6 +39,7 @@ class _MyPhoneState extends State { int? _resendToken; bool _isLoading = false; bool isEmailFieldVisible = false; + final tokenService = TokenStorageService(); @override void initState() { @@ -48,17 +49,18 @@ class _MyPhoneState extends State { clearLocalStorageWhenStarts('initState'); } + clearLocalStorageWhenStarts(fromData) async { print(fromData); - // if(kIsWeb) { - print('vndbbcbdskbvkj3'); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.clear(); - print('Local Storage Clear'); - // } + print('Clearing secure storage on app start'); + + await tokenService.clearAll(); + + print('Secure Storage Cleared'); } + void toggleField() { setState(() { isEmailFieldVisible = !isEmailFieldVisible; @@ -66,8 +68,7 @@ class _MyPhoneState extends State { } Future verifyMobileAndEmailNumber() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.clear(); + await tokenService.clearAll(); print('verifyMobileAndEmailNumber :- Local Storage Clear'); try { if (_formKey.currentState!.validate()) { @@ -115,14 +116,16 @@ class _MyPhoneState extends State { bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { - final SharedPreferences prefs = - await SharedPreferences.getInstance(); if (isEmailFieldVisible) { print('isEmailFieldVisible $isEmailFieldVisible'); - prefs.setString('empEmail', emailMobileController.text); - print('${emailMobileController.text}'); - ToastHelper.showSuccessToast( - context, 'Verification code sent to ${emailMobileController.text}'); + await tokenService.writeValue( + 'empEmail', + emailMobileController.text, + ); + + // print('${emailMobileController.text}'); + // final message = 'Verification code sent to ${emailMobileController.text}'; + ToastHelper.showSuccessToast(context, 'Verification code sent to ${emailMobileController.text}'); Navigator.push( context, MaterialPageRoute( @@ -133,8 +136,8 @@ class _MyPhoneState extends State { ), ); } else { - ToastHelper.showSuccessToast( - context, 'Verification code sent to ${emailMobileController.text}'); + // final message = 'Verification code sent to ${emailMobileController.text}'; + ToastHelper.showSuccessToast(context, 'Verification code sent to ${emailMobileController.text}'); Navigator.push( context, MaterialPageRoute( @@ -154,6 +157,13 @@ class _MyPhoneState extends State { ToastHelper.showErrorToast(context, message); print('Invalid mobile number'); } + } else if (response.statusCode == 429) { + setState(() { + _isLoading = false; + }); + Map data = json.decode(response.body); + final message = data['message']; + ToastHelper.showErrorToast(context, message); } else { setState(() { _isLoading = false; @@ -297,606 +307,267 @@ class _MyPhoneState extends State { @override Widget build(BuildContext context) { - Size _size = MediaQuery.of(context).size; - EdgeInsets marginInsets = EdgeInsets.zero; + final Size _size = MediaQuery.of(context).size; - if (Responsive.isDesktop(context)) { - marginInsets = const EdgeInsets.only( - left: 0, - right: 0, - bottom: 0, - top: 0, - ); - } else if (Responsive.isMobile(context)) { - marginInsets = const EdgeInsets.only( - left: 25, // Example value for mobile - right: 25, // Example value for mobile - bottom: 0, // Example value for mobile - top: 0, // Example value for mobile - ); - } else if (Responsive.isTablet(context)) { - marginInsets = const EdgeInsets.only( - left: 25, // Example value for mobile - right: 25, // Example value for mobile - bottom: 0, // Example value for mobile - top: 0, // Example value for mobile - ); - } return Scaffold( - body: SingleChildScrollView( - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, - child: Container( - height: _size.height, - color: Colors.white, - child: Stack( - children: [ - Visibility( - visible: _size.width <= 1100, - child: ClipRRect( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(30), - bottomRight: Radius.circular(30), - ), - child: Container( - height: _size.height / 3, - width: double.infinity, - color: Color(0xFF00989E), - child: Stack( - children: [ - Positioned( - top: 40, // Adjust top position as needed - left: 10, // Align to the right - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - // Add your navigation logic here - // For example, you can use Navigator.push to navigate to another page - Navigator.pushNamed(context, 'hrLogin'); - }, + body: Container( + width: double.infinity, + height: _size.height, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + Color(0xFF00B6AC), + Color(0xFF83E0DE), + Color(0xFF01B4A8), + ], + ), + ), + + child: Center( + child: Container( + width: double.infinity, // ✅ fixed web width + height: _size.height, // ✅ fixed web height (IMPORTANT) + margin: const EdgeInsets.all(60), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(40)), + ), + child: Row( + children: [ + // ================= LEFT IMAGE ================= + Expanded( + flex: 5, + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(40), + bottomLeft: Radius.circular(40), + ), + child: Image.asset( + 'assets/hrLogin.png', + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + ), + ), + + // ================= RIGHT FORM ================= + Expanded( + flex: 7, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/Nhance-Logo-Final 1.png', + width: 300, + height: 100, + ), + SizedBox(height: 25), + Container( + margin: EdgeInsets.symmetric( + horizontal: 150), child: Row( + mainAxisAlignment: + MainAxisAlignment.center, children: [ - Icon( - Icons.west, // Icon for customer login - color: Colors - .black, // Adjust color as needed - ), - SizedBox(width: 5), - Text( - 'Member Login', - style: TextStyle( - color: Color(0xFF000000), // Text color - // Add other text styles as needed + Expanded( + child: Text( + "Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family", + style: GoogleFonts.poppins( + fontSize: 12, + color: Color(0xFF000000)), + textAlign: TextAlign.center, ), - ), + ) ], ), ), - ), - ), - ), - Column( - children: [ - SizedBox( - height: _size.height / - 6.4), // Adjust the spacing between the rows - Row( - mainAxisAlignment: MainAxisAlignment - .center, // Align to the center - children: [ - Expanded( - flex: Responsive.isDesktop(context) ? 10 : 12, - child: Align( - alignment: Responsive.isDesktop(context) - ? Alignment.centerLeft - : Alignment.bottomCenter, - child: Image.asset( - _size.width <= 1100 - ? 'assets/mobileViewLogo.png' - : 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 150, - ), - ), + SizedBox(height: 20), + Container( + height: 55, + margin: const EdgeInsets.symmetric(horizontal: 150) , + decoration: BoxDecoration( + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), ), - if (!Responsive.isMobile(context) && - !Responsive.isTablet(context)) - Expanded( - flex: 2, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - // Add your navigation logic here - // For example, you can use Navigator.push to navigate to another page - Navigator.pushNamed( - context, 'hrLogin'); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment - .end, // Align to the end (right) - children: [ - Text( - 'HR Login', - style: TextStyle( - color: Color( - 0xFF000000), // Text color - // Add other text styles as needed - ), - ), - SizedBox(width: 5), - Icon( - Icons - .east, // Icon for customer login - color: Colors - .black, // Adjust color as needed - ), - ], - ), - ), + child: TextFormField( + controller: emailMobileController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: "Email / Mobile Number ", + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), - ), - ], - ), - ], - ), - ], - ), - ), - ), - ), - Container( - margin: marginInsets, - alignment: Alignment.bottomCenter, - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - children: [ - Row( - children: [ - if (_size.width > 1100) - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints constraints) { - if (constraints.maxWidth > 600) { - return Image.asset( - 'assets/hrLogin.jpg', - height: _size.height, - fit: BoxFit.cover, - ); - } else { - return SizedBox(); + validator: (value) { + if (value == null || value.trim().isEmpty) { + return "Please enter email or mobile number"; + } + + String input = value.trim(); + + // ❌ Reject all spaces + if (input.contains(' ')) { + return "No spaces allowed"; + } + + final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$'); + final mobileRegex = RegExp(r'^[0-9]{10}$'); + + bool isEmailFormat = emailRegex.hasMatch(input); + bool isMobileFormat = mobileRegex.hasMatch(input); + + // --------------------------- + // 🛑 MOBILE VALIDATION + // --------------------------- + if (RegExp(r'^[0-9]+$').hasMatch(input)) { + if (input.length != 10) { + return "Mobile number must be exactly 10 digits"; + } + } + + // --------------------------- + // 🛑 EMAIL VALIDATION + // --------------------------- + + // Reject anything that has '@' but is NOT a valid email format + if (input.contains('@') && !isEmailFormat) { + return "Enter a valid email address"; + } + + // Reject email with extra digits at the end + if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) { + return "Email cannot contain extra numbers"; + } + + // Reject email+mobile combination + if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) { + return "Enter only email OR mobile number"; + } + + // --------------------------- + // 🛑 MIXED CONTENT (letters + digits but NOT email) + // --------------------------- + bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input); + bool hasDigits = RegExp(r'[0-9]').hasMatch(input); + + if ((hasLetters && hasDigits) && !input.contains('@')) { + return "Enter only email OR 10-digit mobile number"; + } + + // --------------------------- + // 🟢 FINAL CHECK + // --------------------------- + if (!isEmailFormat && !isMobileFormat) { + return "Enter a valid email or 10-digit mobile number"; + } + + return null; } - }, + ), ), - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: Container( - margin: _size.width > 1100 - ? EdgeInsets.only(left: 20, right: 20) - : EdgeInsets.only(left: 0, right: 0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (!Responsive.isMobile(context) && - !Responsive.isTablet(context)) - Row( - children: [ - // Expanded( - // flex: 4, - // child: Align( - // alignment: Alignment.centerLeft, - // child: AnimatedButton( - // animatedOn: - // AnimatedOn.onHover, - // onPress: () { - // Navigator.pushNamed( - // context, 'phone'); - // }, - // onChanges: (change) {}, - // height: 30, - // width: 150, - // text: 'Member Login', - // isReverse: true, - // selectedTextColor: - // Colors.black, - // transitionType: TransitionType - // .RIGHT_CENTER_ROUNDER, - // textStyle: - // GoogleFonts.poppins( - // fontSize: 16, - // letterSpacing: 0, - // color: Color(0xFF00989E), - // fontWeight: FontWeight.w300, - // ), - // backgroundColor: Colors.white, - // selectedBackgroundColor: - // Color(0xFF00989E), - // borderColor: - // Color(0xFF00989E), - // borderWidth: 1, - // ), - // )), - Expanded( - flex: 8, - child: Align( - alignment: Alignment.center, // Align to the start - child: _size.width <= 1100 - ? Image.asset( - 'assets/Nhance-Logo-Final-mobile.png', - width: 150, - height: 70, - ) - : _size.width > 1100 - ? Image.asset( - 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 70, - ) - : Image.asset( - 'assets/Nhance-Logo-Final 1.png', - width: 150, - height: 70, - ), - )), - ], - ), - SizedBox(height: 80), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric(horizontal: 0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Expanded( - child: Text( - "Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family", - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000)), - textAlign: TextAlign.center, - ), - ) - ], + SizedBox(height: 20), + Container( + margin: EdgeInsets.symmetric( + horizontal: 150), + child: SizedBox( + width: double.infinity, + height: 45, + child: ElevatedButton( + style: ElevatedButton + .styleFrom( + backgroundColor: + Color(0xFF00989E), + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular(10), ), ), - SizedBox( - height: 20, + onPressed: _isLoading + ? null + : verifyMobileAndEmailNumber, + child: _isLoading + ? const CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation< + Color>( + Color(0xFF00989E), + ), + ) + : Text( "Login with Email / Mobile OTP", + style: GoogleFonts + .poppins( + color: const Color( + 0xFFFFFFFF), + ), ), - Column( - children: [ - Container( - height: 55, - margin: Responsive.isDesktop(context) - ? const EdgeInsets.symmetric(horizontal: 150) - : const EdgeInsets.symmetric(horizontal: 0), - decoration: BoxDecoration( - border: Border.all(width: 1, color: Colors.grey), - borderRadius: BorderRadius.circular(10), - ), - child: TextFormField( - controller: emailMobileController, - keyboardType: TextInputType.text, - decoration: const InputDecoration( - border: InputBorder.none, - hintText: "Email / Mobile Number ", - contentPadding: EdgeInsets.symmetric(horizontal: 10), - ), - validator: (value) { - if (value == null || value.trim().isEmpty) { - return "Please enter email or mobile number"; - } - - String input = value.trim(); - - // ❌ Reject all spaces - if (input.contains(' ')) { - return "No spaces allowed"; - } - - final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$'); - final mobileRegex = RegExp(r'^[0-9]{10}$'); - - bool isEmailFormat = emailRegex.hasMatch(input); - bool isMobileFormat = mobileRegex.hasMatch(input); - - // --------------------------- - // 🛑 MOBILE VALIDATION - // --------------------------- - if (RegExp(r'^[0-9]+$').hasMatch(input)) { - if (input.length != 10) { - return "Mobile number must be exactly 10 digits"; - } - } - - // --------------------------- - // 🛑 EMAIL VALIDATION - // --------------------------- - - // Reject anything that has '@' but is NOT a valid email format - if (input.contains('@') && !isEmailFormat) { - return "Enter a valid email address"; - } - - // Reject email with extra digits at the end - if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) { - return "Email cannot contain extra numbers"; - } - - // Reject email+mobile combination - if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) { - return "Enter only email OR mobile number"; - } - - // --------------------------- - // 🛑 MIXED CONTENT (letters + digits but NOT email) - // --------------------------- - bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input); - bool hasDigits = RegExp(r'[0-9]').hasMatch(input); - - if ((hasLetters && hasDigits) && !input.contains('@')) { - return "Enter only email OR 10-digit mobile number"; - } - - // --------------------------- - // 🟢 FINAL CHECK - // --------------------------- - if (!isEmailFormat && !isMobileFormat) { - return "Enter a valid email or 10-digit mobile number"; - } - - return null; - } - + ), + ), + ), + SizedBox(height: 20), + Container( + width: double + .infinity, // Make the footer full width + child: Container( + alignment: Alignment.bottomCenter, + padding: + EdgeInsets.symmetric(vertical: 8), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + 'By continuing, you agree with our ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + children: [ + TextSpan( + text: 'privacy policy ', + style: GoogleFonts.poppins( + color: Color(0xFFE26828), + fontSize: 9, ), ), - SizedBox(height: 15), - Container( - margin: Responsive.isDesktop( - context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: SizedBox( - width: double.infinity, - height: 45, - child: ElevatedButton( - style: ElevatedButton - .styleFrom( - backgroundColor: - Color(0xFF00989E), - shape: - RoundedRectangleBorder( - borderRadius: - BorderRadius - .circular(10), - ), - ), - onPressed: _isLoading - ? null - : verifyMobileAndEmailNumber, - child: _isLoading - ? CircularProgressIndicator( - valueColor: - AlwaysStoppedAnimation< - Color>( - Color(0xFF00989E), - ), - ) - : Text( "Login with Email / Mobile OTP", - style: GoogleFonts - .poppins( - color: Color( - 0xFFFFFFFF), - ), - ), - ), + TextSpan( + text: 'and ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + ), + TextSpan( + text: 'terms of use', + style: GoogleFonts.poppins( + color: Color(0xFFE26828), + fontSize: 9, ), ), - SizedBox(height: 15), - // MouseRegion( - // cursor: SystemMouseCursors.click, - // child: GestureDetector( - // onTap: toggleField, - // child: Text( - // isEmailFieldVisible - // ? "Login with Mobile No" - // : "Login with Email", - // style: TextStyle( - // color: Color(0xFF00989E), - // ), - // ), - // ), - // ), ], ), - SizedBox( - height: _size.width <= 1100 ? 0 : 0, - ), - SizedBox( - height: _size.width <= 1100 ? 0 : 0, - ), - SizedBox( - height: _size.width <= 1100 ? 0 : 0, - ), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Column( - // children: [ - // SizedBox(height: 30), - // Text( - // "Benefits of Login", - // style: TextStyle( - // fontSize: 20, - // fontWeight: FontWeight.bold, - // ), - // ), - // SizedBox(height: 15), - // ], - // )) - // : SizedBox(), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.center, - // children: [ - // Expanded( - // flex: 6, - // child: Container( - // padding: - // EdgeInsets.symmetric( - // vertical: 8), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment - // .center, - // children: [ - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // decoration: - // BoxDecoration( - // border: Border( - // right: - // BorderSide( - // width: 1, - // color: Colors - // .black, - // ), - // ), - // ), - // child: Column( - // children: [ - // Icon( - // Icons - // .policy, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: 10), - // Text( - // "View Policy"), - // ], - // ), - // ), - // ), - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // child: Column( - // children: [ - // Icon(Icons.edit, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: 10), - // Text( - // "Manage Claims"), - // ], - // ), - // ), - // ), - // ], - // ), - // ), - // ), - // ], - // ), - // ) - // : SizedBox( - // height: - // Responsive.isDesktop(context) - // ? _size.height * 0.1 - // : _size.height * 0.2, - // ), - SizedBox( - height: Responsive.isDesktop(context) - ? _size.height * 0.3 - : _size.height * 0.2, - ), - // SizedBox( - // height: _size.height * 0.1, - // ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double - .infinity, // Make the footer full width - child: Container( - alignment: Alignment.bottomCenter, - padding: - EdgeInsets.symmetric(vertical: 8), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: - 'By continuing, you agree with our ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - children: [ - TextSpan( - text: 'privacy policy ', - style: GoogleFonts.poppins( - color: Color(0xFF00989E), - fontSize: 9, - ), - ), - TextSpan( - text: 'and ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - ), - TextSpan( - text: 'terms of use', - style: GoogleFonts.poppins( - color: Color(0xFF00989E), - fontSize: 9, - ), - ), - ], - ), - ), - ), - ), - ), - ], + ), ), ), - ), - ], + ], + ), ), - ], + ), ), - ), + ], ), ), - ], - )), - )); + ) + + ) + ); } } diff --git a/lib/main.dart b/lib/main.dart index 618a560..7a928b0 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,23 +5,27 @@ import 'package:nhancepolicy/addons.dart'; import 'package:nhancepolicy/branch/branch_selection_page.dart'; import 'package:nhancepolicy/empReview.dart'; import 'package:nhancepolicy/empDetails.dart'; -import 'package:nhancepolicy/excel_verification.dart'; +import 'package:nhancepolicy/presentation/preFileUpload.dart'; import 'package:nhancepolicy/hrHome.dart'; import 'package:nhancepolicy/hrVerify.dart'; import 'package:nhancepolicy/phone.dart'; -import 'package:nhancepolicy/postFileUpload.dart'; +import 'package:nhancepolicy/presentation/excelVerification.dart'; +import 'package:nhancepolicy/presentation/postFileUpload.dart'; +import 'package:nhancepolicy/presentation/cdList.dart'; +import 'package:nhancepolicy/presentation/claims.dart'; +import 'package:nhancepolicy/presentation/policies.dart'; import 'package:nhancepolicy/service/session/session_service.dart'; import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:nhancepolicy/verify.dart'; import 'package:nhancepolicy/home.dart'; -import 'package:nhancepolicy/hrDashboard.dart'; +import 'package:nhancepolicy/presentation/hrDashboard.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:nhancepolicy/hrLogin.dart'; -import 'package:nhancepolicy/hrPolicyDetails.dart'; +import 'package:nhancepolicy/presentation/hrPolicyDetails.dart'; import 'package:nhancepolicy/oldPolicy.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'cdTransactionDetails.dart'; +import 'presentation/cdTransactionDetails.dart'; import 'config/environment.dart'; import 'email_verify.dart'; @@ -96,7 +100,7 @@ Future startApp() async { // onResendCode: (String, int) {}, // ), 'hrHome': (context) => MyHrHome(), - 'excelVerify': (context) => const excelVerify( + 'preFileUpload': (context) => const preFileUpload( ClientId: '', policyTypeId: '', ClientPoliyId: '', @@ -108,6 +112,7 @@ Future startApp() async { cardInsurer_name: '', cardPolicy_name: '', cardPolicy_ExpDate: '', + total_premium: '', ), 'postFileUpload': (context) => const postFileUpload( ClientId: '', @@ -121,23 +126,22 @@ Future startApp() async { cardInsurer_name: '', cardPolicy_name: '', cardPolicy_ExpDate: '', + total_premium: '', + ), + 'excelErrorScreen': (context) => const excelErrorScreen( + ClientId: '', + policy_no: '', + action: '', + created_at: '', + clientBranchId: '', + Token: '', + TokenType: '', + id: '' ), 'empDetails': (context) => empDetails(), 'addOnsDetails': (context) => addOnsDetails(), 'empReviewDetails': (context) => empReviewDetails(), - 'hrDashboard': (context) => hrDashboard( - selectedIndex: 0, - isHrcode: 0, - empCodeFromHrPolicy: '', - ), - 'cdTransactionDetails': (context) => cdTransactionDetails( - insurerName: '', - cdMasterAccountNo: '', - insurerId: '', - cd_ac_pk: '', - empClientId: '', - postToken: '', - ), + 'hrDashboard': (context) => hrDashboard(), 'hrPolicyDetails': (context) => hrPolicyDetails( ClientId: '', policyTypeId: '', @@ -150,9 +154,21 @@ Future startApp() async { cardInsurer_name: '', cardPolicy_name: '', cardPolicy_ExpDate: '', + total_premium: '', + is_ecard_bulk_download_for_employee: 0, ), 'oldPolicy': (context) => oldPolicy(), 'branchSelection': (context) => BranchSelectionPage(), + 'policies': (context) => policies(), + 'CdPoliciesList': (context) => CdPoliciesList(), + 'ClaimsPolicies': (context) => ClaimsPolicies(), + 'cdTransactionDetails': (context) => cdTransactionDetails( + insurerName: '', + cdMasterAccountNo: '', + insurerId: '', + cd_ac_pk: '', + empClientId: '', + ), }, )); } diff --git a/lib/postFileUpload.dart b/lib/postFileUpload.dart deleted file mode 100755 index 3aa7ae7..0000000 --- a/lib/postFileUpload.dart +++ /dev/null @@ -1,1262 +0,0 @@ -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:jwt_decode/jwt_decode.dart'; -import 'package:nhancepolicy/customAppBar/customAppBar.dart'; -import 'dart:convert'; -import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:nhancepolicy/customAppBar/toastHelper.dart'; -import 'package:nhancepolicy/hrPolicyDetails.dart'; -import 'package:nhancepolicy/service/api_service.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:universal_html/html.dart' as html; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'dart:io'; -import 'package:intl/intl.dart'; -import 'package:csv/csv.dart'; - -import 'package:spreadsheet_decoder/spreadsheet_decoder.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'config/environment.dart'; -import 'customAppBar/customFooter.dart'; - -class postFileUpload extends StatefulWidget { - final String ClientId; - final String policyTypeId; - final String ClientPoliyId; - final String clientBranchId; - final String Token; - final String TokenType; - final String cardType; - final String cardPolicyNo; - final String cardInsurer_name; - final String cardPolicy_name; - final String cardPolicy_ExpDate; - const postFileUpload( - {Key? key, - required this.ClientId, - required this.policyTypeId, - required this.ClientPoliyId, - required this.clientBranchId, - required this.Token, - required this.TokenType, - required this.cardType, - required this.cardPolicyNo, - required this.cardInsurer_name, - required this.cardPolicy_name, - required this.cardPolicy_ExpDate}) - : super(key: key); - - @override - State createState() => _postFileUploadState(); -} - -class _postFileUploadState extends State { - Uint8List? fileBytes; - Uint8List? fileBytes2; - late String _token; - dynamic getPolicyNo; - bool _isLoading = false; - dynamic getPolicyNameDetails; - dynamic clintID; - String? fileName; - int _currentStep = 0; // Step index tracker - List dataPolicy = []; - dynamic validationArray = []; - dynamic missingColumnErrorMsg = 0; - dynamic columnIndexMismatchCount = 0; - dynamic columnMissingCount = 0; - List> extractedData = []; - dynamic argumentsData; - List> originalData = []; // Original data source - List> filteredData = []; // Filtered data source - List> tableData = []; // Filtered data source - - List> nonExcelFilteredData = []; - dynamic invalidRelationships = 0; - dynamic dobAgeCheckCount = 0; - dynamic empRefId; - dynamic empPrimaryId; - List> getFileUploadMasterList = []; - dynamic getThrFileList = []; - late int excelValidationStaus = 1; - bool isSuccess = false; - String successContent = ''; - bool isLoading = false; - late ApiService apiService; - TextEditingController searchController = TextEditingController(); - String? _selectedOption; - - int _currentPage = 1; - int _rowsPerPage = 5; - - List get _paginatedData { - final startIndex = (_currentPage - 1) * _rowsPerPage; - final endIndex = - (_currentPage * _rowsPerPage).clamp(0, filteredData.length); - return filteredData.sublist(startIndex, endIndex); - } - - final List> serviceList = [ - {"id": 1, "name": "Sales"}, - {"id": 2, "name": "Service"}, - ]; - - String? selectedKey; - String? selectedValue; - String? _selectedAction; - - @override - void initState() { - super.initState(); - apiService = ApiService(context); - _loadToken(); - getFileUploadMasterDetails(); - getFileListDetails(); - } - - @override - void dispose() { - super.dispose(); - html.window.localStorage.remove('fileBytes'); - } - - Future _loadToken() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - // final token = prefs.getString('hrtoken'); - final token = widget.Token; - if (token != null && token.isNotEmpty) { - setState(() { - _token = token; - }); - Map? decodedToken = Jwt.parseJwt(token); - print('decodedToken $decodedToken'); - } else { - // Token is empty or null, handle accordingly (e.g., navigate to login screen) - // For now, let's navigate to the login screen - ToastHelper.showErrorToast(context, 'Session Out'); - Navigator.pushReplacementNamed(context, 'hrLogin'); - } - } - - // Future getPolicyDetails() async { - // setState(() { - // clientPolicyId = argumentsData['client_policy_id']; - // clientId = argumentsData['client_id']; - // policyType = argumentsData['type']; - // policy_name = argumentsData['policy_name']; - // }); - // } - - // Future _uploadFile1(importPolicyName) async { - // FilePickerResult? result = await FilePicker.platform.pickFiles( - // type: FileType.custom, - // allowedExtensions: ['xlsx', 'xls', 'csv'], - // ); - // - // if (result != null) { - // PlatformFile file = result.files.first; - // Uint8List fileBytes = file.bytes!; - // // Use the fileBytes as needed - // print('File name: ${file.name}'); - // print('File size: ${file.size}'); - // print('File bytes: $fileBytes'); - // _processExcelData(fileBytes); - // } else { - // // User canceled the picker - // } - // } - - Future getFileUploadMasterDetails() async { - print('9'); - try { - final response = await apiService.getFileUploadMastersToApi(widget.Token); - - if (response['status'] == true) { - print('getFileUploadMasterList1'); - setState(() { - final actions = Map.from(response['data']['actions']); - setState(() { - getFileUploadMasterList = actions.entries - .map((e) => {"key": e.key, "value": e.value}) - .toList(); - print('getFileUploadMasterList: $getFileUploadMasterList'); - }); - print('getFileUploadMasterList'); - print(getFileUploadMasterList); - }); - } else { - print('Request failed with status: ${response['code']}'); - } - } catch (e) { - setState(() { - isLoading = false; - }); - print('Exception occurred: $e'); - } finally { - setState(() { - // _isLoading = false; - }); - } - } - - Future getFileListDetails() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - empPrimaryId = prefs.getString('empPrimaryId'); - print('9'); - try { - final response = await apiService.getFileListToApi( - empPrimaryId, widget.cardPolicyNo, widget.Token); - - if (response['status'] == 'success') { - print('getThrFileList'); - setState(() { - getThrFileList = List>.from(response['data']); - originalData = getThrFileList; - filteredData = List.from(originalData); - print('filteredData'); - print(filteredData); - }); - } else { - print('Request failed with status: ${response['code']}'); - } - } catch (e) { - setState(() { - isLoading = false; - }); - print('Exception occurred: $e'); - } finally { - setState(() { - // _isLoading = false; - }); - } - } - - Future getHrFileDownload(id, file_name) async { - // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); - final apiurl = Environment.apiUrlPost; - final String url = '$apiurl/hrFileDownload?id=$id'; - final token = widget.Token; - - final response = await http.get( - Uri.parse(url), - headers: { - 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', - 'Authorization': 'Bearer $token', - 'Content-Type': 'application/json', - // 'app-signature': 'ts-traveltool-2025-signature-123456', - }, - ); - - if (response.statusCode == 200) { - try { - print("PDF Downloaded"); - - // ✅ Create a blob from the response body bytes - final blob = html.Blob([response.bodyBytes]); - - // ✅ Generate a download URL - final url = html.Url.createObjectUrlFromBlob(blob); - - // ✅ Trigger file download automatically - final anchor = html.AnchorElement(href: url) - ..setAttribute('download', '$file_name') - ..click(); - - // ✅ Revoke the URL to free memory - html.Url.revokeObjectUrl(url); - } catch (e) { - throw Exception('Error parsing response: $e'); - } - } else { - print("Download failed with status: ${response.statusCode}"); - } - } - - void _uploadFile() async { - print('Test'); - if (kIsWeb) { - print('kIsWeb'); - final input = html.FileUploadInputElement(); - input.accept = '.xlsx,.xls,.csv'; - input.click(); - input.onChange.listen((event) async { - final file = input.files!.first; - final reader = html.FileReader(); - reader.readAsArrayBuffer(file); - await reader.onLoadEnd.first; // Wait for the file to be loaded - if (reader.readyState == html.FileReader.DONE) { - Uint8List? fileBytes = reader.result as Uint8List?; - if (fileBytes != null) { - setState(() { - fileName = file.name; - }); - // Save fileBytes to local storage - final jsonString = json.encode(fileBytes); - html.window.localStorage['fileBytes'] = jsonString; - print('File Name: $fileName'); - print('File Bytes: $fileBytes'); - sendExcelFIleTOAPI(fileBytes, fileName); - // Call the function to process Excel data here - // _processExcelData(fileBytes, fileName); - } - } - }); - } else { - // Handle non-web platforms here (e.g., show an error message) - print('File upload is only supported on web platforms.'); - } - } - - int countInvalidDobs(List> data) { - int invalidCount = 0; - - for (var entry in data) { - DateTime dob; - - // if (entry['DOB'] is String) { - dob = DateTime.parse(entry['DOB'].toString()); - // } else if (entry['DOB'] is DateTime) { - // dob = entry['DOB']; - // } else { - // // Invalid DOB format, skip this entry - // continue; - // } - print(entry['Relation']); - if ((entry['Relation'].toString() == 'Son' || - entry['Relation'].toString() == 'Daughter')) { - if (DateTime.now().difference(dob).inDays > 25 * 365) { - print('child $dob'); - invalidCount++; - } - } else if ((entry['Relation'] != 'Son' && - entry['Relation'] != 'Daughter')) { - if (DateTime.now().difference(dob).inDays < 18 * 365) { - print('others $dob'); - invalidCount++; - } - } - } - - return invalidCount; - } - - void _dragAndDropFile(html.File file) async { - print('file'); - print(file); - // Prepare form data - final formData = html.FormData(); - formData.appendBlob('file', file); - - // Send formData to API endpoint - final response = await html.HttpRequest.request( - 'your_api_endpoint_here', - method: 'POST', - sendData: formData, - ); - - // Handle response as needed - print(response.responseText); - } - - Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - empPrimaryId = prefs.getString('empPrimaryId'); - // Future.delayed(Duration(seconds: 3), () { - setState(() { - isLoading = true; - }); - // }); - print('submit'); - print(fileBytes); - if (fileBytes == null) { - ToastHelper.showErrorToast(context, 'Please upload file'); - print('return'); - return; // No file selected - } else { - print('else'); - // // Prepare form data - // final formData = html.FormData(); - // formData.appendBlob('file', html.Blob([fileBytes]), fileName); - - // URL of the API where you want to send the file - final apiUrl = Environment.apiUrlPost + 'hrFileUpload'; - print('else'); - // Create a multipart request - final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); - print('else'); - // Attach the file to the request - // Set authorization token in headers - request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; - request.headers['Authorization'] = 'Bearer $_token'; - // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, - // filename: fileName)); - print('Filename: $fileName'); - // request.files.add(http.MultipartFile.fromBytes( - // 'file', - // fileBytes, - // filename: fileName ?? 'default_filename.xlsx', - // )); - request.files.add(http.MultipartFile.fromBytes( - 'file_name', - fileBytes, - filename: fileName ?? 'default_filename.xlsx', - )); - print('clintID: $clintID'); - - request.fields['client_id'] = widget.ClientId; - request.fields['policy_no'] = widget.cardPolicyNo; - request.fields['client_branch_id'] = widget.clientBranchId; - request.fields['file_action'] = selectedKey!; - // request.fields['status'] = selectedKey!; - request.fields['created_by'] = empPrimaryId; - request.fields['policy_id'] = widget.ClientPoliyId; - // "client_id": 1, - // "client_branch_id": 2, - // "policy_no": "POL123456", - // "file_action": , - // "status": "inception", - // "created_by": 10 - - print('request : $request'); - // Send the request - final response = await request.send(); - print('else'); - // Read response stream as a string - final responseString = await response.stream.bytesToString(); - print(responseString); - Map data = json.decode(responseString); - if (data['status'] == true) { - print('upload success'); - setState(() { - isLoading = false; - }); - ToastHelper.showSuccessToast(context, data['message']); - getFileListDetails(); - setState(() { - selectedValue = null; - selectedKey = null; - resetErrorCount(); - }); - } else { - setState(() { - isLoading = false; - }); - ToastHelper.showErrorToast(context, data['message']); - } - } - } - - resetErrorCount() { - setState(() { - fileBytes = null; - fileName = null; - }); - } - - void search(String query) { - print(query); - // Check if the query is empty - if (query.isEmpty) { - // If search query is empty, show all data - setState(() { - filteredData = List.from(originalData); - }); - } else { - // Filter the original data based on the search query - setState(() { - filteredData = originalData.where((row) { - // Implement your filter logic here - // For example, check if any field in the row contains the query - // Adjust this logic based on your data structure - return row['file_name'] - .toString() - .toLowerCase() - .contains(query.toLowerCase()) || - row['file_action'] - .toString() - .toLowerCase() - .contains(query.toLowerCase()) || - row['created_at'] - .toString() - .toLowerCase() - .contains(query.toLowerCase()); - }).toList(); - }); - } - print(filteredData.length); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar(), - backgroundColor: Color(0xFFEFF3F6), - body: Stack(children: [ - // color: Color(0xFFEFF3F6), - - // padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 40), - SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Container( - padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50), - child: Column( - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.center, // 👈 fix alignment - children: [ - Expanded( - flex: 8, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // back button + texts - Row( - children: [ - IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - onPressed: () { - Navigator.pop(context); - }, - icon: Icon( - Icons.arrow_back_ios, - color: Colors.grey, - size: 17, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.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, - ), - ), - ], - ) - ], - ), - ], - ), - ), - Expanded( - flex: 4, - child: Container( - padding: const EdgeInsets.only(top: 4), - child: getFileUploadMasterList.isEmpty - ? const SizedBox() // 👈 avoid crash on first build - : buildDropdownField( - 'File Action', - (value) { - setState(() { - selectedKey = value; - selectedValue = getFileUploadMasterList - .firstWhere((item) => - item['key'] == value)['value']; - }); - debugPrint("Selected Key: $selectedKey"); - debugPrint( - "Selected Value: $selectedValue"); - }, - false, - getFileUploadMasterList, - 'value', - selectedKey, - valueKey: 'key', - ), - ), - ), - ], - ), - ), - 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: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - flex: 12, - child: Column( - children: [ - // Row( - // mainAxisAlignment: - // MainAxisAlignment.center, - // children: [ - // Expanded( - // child: Text( - // '${widget.cardPolicy_name} - (${widget.cardType})', - // textAlign: TextAlign.center, - // style: const TextStyle( - // fontSize: 20, - // fontWeight: FontWeight - // .w600, // Adjust the font size as needed - // color: Color( - // 0xFF181818), // Adjust the text color as needed - // ), - // ), - // ) - // ], - // ), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - margin: EdgeInsets.symmetric( - horizontal: 150), - alignment: Alignment.center, - color: Colors.grey[200], - height: 180, - child: DragTarget( - onAccept: (html.File droppedFile) { - setState(() { - fileName = droppedFile.name; - }); - _dragAndDropFile(droppedFile); - }, - builder: (BuildContext context, - List candidateData, - List rejectedData) { - return Container( - alignment: Alignment.center, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - fileName != null - ? Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .upload_file, // Choose the appropriate icon - size: 35, - color: Color( - 0xFFE26728), // Adjust the size as needed - ), - SizedBox( - height: - 15), // Add some space between the icon and text - Text( - '$fileName', - style: TextStyle( - fontSize: - 16), - ), - SizedBox( - height: - 25), // Add some space between the icon and text - MouseRegion( - cursor: SystemMouseCursors - .click, // Set cursor to pointer on hover - child: - GestureDetector( - onTap: () { - resetErrorCount(); - }, - child: - const Row( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Icon( - Icons - .delete_forever, // Choose the remove icon - size: - 20, // Adjust the size as needed - color: Colors - .red, // Set the color of the remove icon - ), - SizedBox( - width: - 1), // Add some space between the icon and text - Text( - 'Remove', - style: TextStyle( - fontSize: - 13, - color: - Color(0xFF727272)), - ), - ], - ), - ), - ) - ], - ) - : ElevatedButton( - onPressed: () => { - print( - 'selectedValue $selectedValue'), - if (selectedValue != - null) - { - _uploadFile(), - } - else - { - ToastHelper - .showErrorToast( - context, - 'Please select file action') - } - }, - child: Text( - 'Select File', - style: TextStyle( - color: Colors - .white), - ), - style: - ElevatedButton - .styleFrom( - backgroundColor: - Color( - 0xFFE26728), - ), - ), - SizedBox(height: 20), - if (fileName == null) - Text( - 'Supported Files : XLSX') - // Add more Text widgets for additional lines of text - ], - ), - ); - }, - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ], - ), - ), - SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.circular(10), // 👈 set your desired radius - ), - // width: double.infinity, // take full width - // height: MediaQuery.of(context).size.height * 0.8, // 80% of screen height - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Row( - children: [ - Expanded( - flex: 4, - child: Align( - alignment: Alignment.centerLeft, - child: Container( - width: 400, - height: 37, - decoration: BoxDecoration( - color: Color(0xFFF0F0F0), - borderRadius: BorderRadius.circular(8), - ), - child: TextField( - decoration: InputDecoration( - hintText: 'Search', - prefixIcon: Icon(Icons.search, size: 18), - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 8), - border: InputBorder - .none, // No border since Container handles it - ), - controller: searchController, - onChanged: search, - style: TextStyle(fontSize: 14), - ), - ), - ), - ), - ], - ), - SizedBox(height: 20), - Row( - children: [ - Expanded( - child: _buildFileUploadedListTable(context), - ) - ], - ), - ], - ), - ), - ], - ), - ), - ), - - if (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 - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double.infinity, // Make the footer full width - child: CustomFooter(), - ), - ), - ]), - ); - } - - Widget buildDropdownField( - String label, - void Function(T?) onChanged, - bool readOnly, - List> itemsList, - String displayField, - T? selectedValue, { - String valueKey = 'id', - }) { - return DropdownButtonFormField( - value: selectedValue, - decoration: InputDecoration(labelText: label), - items: itemsList.map>((item) { - return DropdownMenuItem( - value: item[valueKey] as T, // ✅ cast to generic type - child: - Text(item[displayField].toString()), // always display as String - ); - }).toList(), - onChanged: readOnly ? null : onChanged, - validator: (value) { - if (value == null) { - return 'Please select a $label'; - } - return null; - }, - ); - } - - Widget _buildFileUploadedListTable(BuildContext context) { - // Loader overlay - - if (filteredData.isEmpty) { - return const SizedBox( - height: 50, - child: Center(child: Text('No available data')), - ); - } - - print('filteredData12345 $filteredData'); - - return ListView.builder( - shrinkWrap: true, // important for nested lists - physics: const AlwaysScrollableScrollPhysics(), - itemCount: _paginatedData.length + 2, - itemBuilder: (context, index) { - if (index == 0) return _buildHeader(); - if (index == _paginatedData.length + 1) - return _buildPagination(context); - - final item = _paginatedData[index - 1]; - return _buildDataRow(item); - }, - ); - - // 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( - // 'Insurer Name', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // 'CD Account number', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 2, - // child: Text( - // 'Current Balance', - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // Expanded( - // flex: 1, - // child: Text( - // 'Action', - // textAlign: TextAlign.center, - // style: GoogleFonts.poppins( - // color: Colors.white, fontWeight: FontWeight.bold), - // ), - // ), - // ], - // ), - // ), - // - // 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, - // borderRadius: BorderRadius.circular(6), - // ), - // child: Row( - // children: [ - // Expanded( - // flex: 4, - // child: Text( - // item['insurer_name'] ?? '-', - // style: GoogleFonts.poppins(color: Color(0xFF000000)), - // ), - // ), - // Expanded( - // flex: 3, - // child: Text( - // item['cd_master_account_no'] ?? '-', - // style: GoogleFonts.poppins(color: Color(0xFF000000)), - // ), - // ), - // Expanded( - // flex: 2, - // child: Text( - // "₹${item['balance'] ?? '0'}", - // style: GoogleFonts.poppins(color: Color(0xFF000000)), - // ), - // ), - // Expanded( - // flex: 1, - // child: Center( - // child: IconButton( - // icon: const Icon(Icons.remove_red_eye_outlined), - // onPressed: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => cdTransactionDetails( - // insurerName: item['insurer_name'], - // cdMasterAccountNo: item['cd_master_account_no'], - // insurerId: item['insurer_id'], - // cd_ac_pk: item['cd_ac_pk'], - // empClientId: widget.empClientId, - // postToken: widget.postToken), - // ), - // ); - // }, - // ), - // ), - // ), - // ], - // ), - // ); - // }).toList(), - // - // - // ], - // ); - } - - Widget _buildDataRow(Map item) { - print('item $item'); - return Container( - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), - margin: const EdgeInsets.only(top: 10), - decoration: BoxDecoration( - color: Color(0xFFE0F7F9), - border: const Border( - bottom: BorderSide(color: Color(0xFFD7E9EB), width: 1), - ), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - Expanded( - flex: 4, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MouseRegion( - cursor: SystemMouseCursors.click, // 👆 Pointer cursor - child: Tooltip( - message: "Click to Download", // 📝 Tooltip text - child: GestureDetector( - onTap: () { - getHrFileDownload( - item['id'], - item[ - 'file_name']); // ✅ Call your API/download logic - }, - child: Text( - item['file_name'] ?? '-', - style: _dataBold.copyWith( - color: Colors.blue, // 🔗 Make it look like a link - decoration: TextDecoration.underline, - ), - ), - ), - ), - ), - ], - ), - ), - Expanded( - flex: 3, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(item['file_action'] ?? '-', style: _dataBold), - ], - ), - ), - Expanded( - flex: 2, - child: Text(formatDate(item['created_at']), style: _dataBold), - ), - Expanded( - flex: 2, - child: Text(item['status'], style: _dataBold), - ), - ], - ), - ); - } - - 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('File Name', style: _headerStyle)), - Expanded(flex: 3, child: Text('File Action', style: _headerStyle)), - Expanded(flex: 2, child: Text('Created At', style: _headerStyle)), - Expanded(flex: 2, child: Text('Status', style: _headerStyle)) - ], - ), - ); - } - - static final _dataBold = GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Color(0xFF000000), - ); - - static final _dataSub = GoogleFonts.poppins( - fontSize: 10, - fontWeight: FontWeight.w300, - color: Color(0xFF585757), - ); - - static const _headerStyle = TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ); - - 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()), - ), - ); - } - - String formatDate(String? dateString) { - if (dateString == null || dateString.isEmpty) return '-'; - - try { - DateTime parsedDate = DateTime.parse(dateString); - return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); - } catch (e) { - return '-'; - } - } -} diff --git a/lib/presentation/RaiseClaimForm.dart b/lib/presentation/RaiseClaimForm.dart new file mode 100644 index 0000000..60c8775 --- /dev/null +++ b/lib/presentation/RaiseClaimForm.dart @@ -0,0 +1,1494 @@ +import 'dart:convert'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; + +import '../config/environment.dart'; +import '../customAppBar/toastHelper.dart'; +import '../responsive.dart'; +import '../service/api_service.dart'; +import '../service/file_upload_service.dart'; +import 'package:http/http.dart' as http; + +import 'package:file_picker/file_picker.dart'; + +import '../service/multi_file_upload_widget.dart'; +import '../service/token_storage_service.dart'; +import 'package:pdf/widgets.dart' as pw; + +class RaiseClaimDialog extends StatefulWidget { + final BuildContext parentContext; + final VoidCallback onSuccess; + + const RaiseClaimDialog({Key? key, required this.parentContext, required this.onSuccess,}) + : super(key: key); + + @override + State createState() => _RaiseClaimDialogState(); +} + +class _RaiseClaimDialogState extends State { + // const RaiseClaimDialog({super.key}); + late ApiService apiService; + bool isLoading = false; + dynamic empPrimaryId; + dynamic empClientId; + dynamic empClientBranchId; + dynamic empHrId; + final tokenService = TokenStorageService(); + String? _postPreToken = ''; + List> policyList = []; + dynamic policyDataIsEmpty = 1; + dynamic policyHeading; + dynamic policyName; + dynamic EmployeePolicy; + List> employeeDetails = []; + dynamic argumentsData; + dynamic claimsDetails; + int? fromClaimsPage; + dynamic decodedToken; + List> departmentList = []; + List> policyNumberList = []; + int? serviceId; + String? serviceName; + int? policyNumberId; + int? selectedMemberId; + String? selectedMemberName; + String? selectedServiceName; + String? selectedPolicyNo; + dynamic claimsDepartmentId; + dynamic claimSubject; + dynamic rawPolicyStartDate; + dynamic rawPolicyEndDate; + dynamic claimsPolicyNo; + dynamic client_branch_id; + dynamic mobileNo; + dynamic client_policy_id; + dynamic selfDetails; + dynamic emailId; + List> employeePolicyList = []; + String? selectedFileNames; + // html.File? uploadedFile; + // List uploadedFiles = []; + List uploadedFiles = []; + final fileService = FileUploadService(); + bool isSubmitting = false; + dynamic getPostCardArrays = []; + List> activePoliciesList = []; + int stausVal = 1; + Map getClaimPoliciesApi = {}; + int? selectedPolicyTypeId; + int? selectedClientPolicyId; + + Map? selectedMemberObject; + + // Declare subjectController and bodyController as instance variables + late TextEditingController subjectController; + late TextEditingController messageController; + late TextEditingController accidentDetailsController; + late TextEditingController hospitalNameController; + late TextEditingController hospitalAddressController; + late TextEditingController hospitalCityController; + late TextEditingController hospitalStateController; + late TextEditingController hospitalPinCodeController; + late TextEditingController hospitalPhoneNoController; + late TextEditingController claimAmountController; + late TextEditingController sumInsuredController; + late TextEditingController admitDateController; + late TextEditingController dischargeDateController; + + DateTime? accidentDate; + DateTime? deathDate; + DateTime? intimationDate; + DateTime? birthDate; + DateTime? admitDate; + DateTime? dischargeDate; + + DateTime? parsedPolicyStartDate; + DateTime? parsedPolicyEndDate; + + Map selectedDates = { + "Accident Date": null, + "Date of Death": null, + }; + + final Map> servicePolicyTypeMap = { + 1: [2, 3, 4, 5], + 2: [1], + 3: [6], + 4: [7], + }; + + List> filteredPoliciesList = []; + + // Create a unique form key for each accordion section + final GlobalKey formKey = GlobalKey(); + + bool isServiceValid = true; + bool isPolicyValid = true; + bool isMemberValid = true; + bool isSubjectValid = true; + bool isHospitalNameValid = true; + bool isHospitalAddressValid = true; + bool isHospitalCityValid = true; + bool isHospitalStateValid = true; + bool isHospitalPincodeValid = true; + bool isHospitalPhoneNoValid = true; + bool isAdmitDischargeValid = true; + bool isClaimAmountValid = true; + bool isAccidentDateValid = true; + bool isIntimationDateValid = true; + bool isAdmitDateValid = true; + bool isDischargeDateValid = true; + + @override + void initState() { + super.initState(); + fileService.clearAll(); + subjectController = TextEditingController(); + messageController = TextEditingController(); + accidentDetailsController = TextEditingController(); + hospitalNameController = TextEditingController(); + hospitalAddressController = TextEditingController(); + hospitalCityController = TextEditingController(); + hospitalStateController = TextEditingController(); + hospitalPinCodeController = TextEditingController(); + hospitalPhoneNoController = TextEditingController(); + claimAmountController = TextEditingController(); + sumInsuredController = TextEditingController(); + admitDateController = TextEditingController(); + dischargeDateController = TextEditingController(); + apiService = ApiService(context); // Initialize ApiService here + _loadToken(); + // Check if fromClaimsPage is 0 and set serviceId to 'Claims' (id is 2) + // if (fromClaimsPage == 0) { + // setInitialServiceId(); + // } + } + + @override + void dispose() { + // Dispose the controllers to avoid memory leaks + subjectController.dispose(); + messageController.dispose(); + accidentDetailsController.dispose(); + hospitalNameController.dispose(); + hospitalAddressController.dispose(); + hospitalCityController.dispose(); + hospitalStateController.dispose(); + hospitalPinCodeController.dispose(); + hospitalPhoneNoController.dispose(); + claimAmountController.dispose(); + sumInsuredController.dispose(); + admitDateController.dispose(); + dischargeDateController.dispose(); + serviceId = null; + departmentList.clear(); + super.dispose(); + fileService.clearAll(); + } + + Future _loadToken() async { + _postPreToken = tokenService.getCurrentToken(); + empClientId = await tokenService.readValue('empClientId'); + empClientBranchId = await tokenService.readValue('empClientBranchId'); + empHrId = await tokenService.readValue('empHrId'); + getClaimsPoliciesDetails(); + await getPostCashDepositDetails( + empClientBranchId, empClientId, empHrId, _postPreToken); + } + + Future getClaimsPoliciesDetails() async { + print('9'); + setState(() { + isLoading = true; + }); + try { + print('10'); + final response = await apiService.getClaimPoliciesToApi(_postPreToken!); + if (response['status'] == 'success') { + setState(() { + isLoading = false; + }); + setState(() { + getClaimPoliciesApi = Map.from(response['data']); + departmentList = (getClaimPoliciesApi['ticket_type'] as List) + .map>((item) => { + 'id': int.parse(item['ticket_type'].toString()), + 'name': item['type_name'].toString(), + }) + .toList(); + }); + } else { + setState(() { + isLoading = false; + }); + + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + // isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + // _isLoading = false; + }); + } + } + + 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; + // }); + try { + if (empClientBranchId == null || empClientId == null) { + return; + } + final response = await apiService.getActiveCashDepositDetailsToApi( + empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal); + + // final response = await apiService.getCashDepositDetailsToApi( + // clintID!, clintBranchId!, hr_id, token); + print('IN1'); + if (response['status'] == 'success') { + isLoading = false; + setState(() { + print('response'); + print(response['data']); + getPostCardArrays = List>.from(response['data']); + print('getPostCardArrays'); + print(getPostCardArrays); + + activePoliciesList = getPostCardArrays; + }); + + print('IN2'); + } else { + isLoading = false; + print('API request failed with status'); + setState(() { + activePoliciesList = []; + }); + print('API request failed with status'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + void filterPoliciesByService(int serviceTicketType) { + final allowedPolicyTypes = servicePolicyTypeMap[serviceTicketType] ?? []; + + final filtered = activePoliciesList.where((policy) { + final policyTypeId = int.tryParse( + policy['policy_type_id'].toString(), + ); + return allowedPolicyTypes.contains(policyTypeId); + }).toList(); + + setState(() { + policyNumberId = null; + + policyNumberList = filtered.map>((policy) { + return { + // ✅ VALUE (what gets sent) + 'id': int.parse(policy['client_policy_id'].toString()), + + // ✅ DISPLAY (what user sees) + 'label': '${policy['type']} - ${policy['policy_no']}', + + // keep extras if needed + 'policy_type_id': int.parse(policy['policy_type_id'].toString()), + }; + }).toList(); + + + isPolicyValid = true; + }); + } + + Future getCDPoliciesDetails() async { + print('9'); + setState(() { + isLoading = true; + }); + try { + print('10'); + + final response = await apiService.getEmployeeAndDependenceToApi( + empClientId, selectedClientPolicyId, empClientBranchId, _postPreToken!); + + if (response['status'] == 'success') { + final List> members = + List>.from(response['data']); + + setState(() { + employeePolicyList = members.map((m) { + return { + 'id': int.parse(m['id'].toString()), // ✅ UNIQUE + 'name': m['name'], // ✅ DISPLAY + 'client_policy_id': m['client_policy_id'] ?? '', + 'emp_id': m['self_employee_id'] ?? '', + 'insured_emp_id': m['employee_id'] ?? '', + 'emp_code': m['emp_code'] ?? '', + 'mobile': m['mobile'] ?? ' ', + 'empEmailCorporate': m['email_corporate'] ?? '', + 'relationship': m['relationship'], + 'gender': m['gender'], + 'dob': m['dob'], + 'policy_name': serviceId == 1 + ? null + : m['policy_name'], + }; + }).toList(); + }); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + isLoading = false; + }); + } + } + + Future sendFormDataToApi() async { + + setState(() { + isServiceValid = serviceId != null; + isPolicyValid = selectedClientPolicyId != null; + isMemberValid = selectedMemberId != null; + // isSubjectValid = subjectController.text.trim().isNotEmpty; + // 🏥 GMC / Topup + final isGmc = serviceId == 1 || serviceId == 4; + isHospitalNameValid = !isGmc || hospitalNameController.text.trim().isNotEmpty; + isHospitalAddressValid = !isGmc || hospitalAddressController.text.trim().isNotEmpty; + isHospitalCityValid = !isGmc || hospitalCityController.text.trim().isNotEmpty; + isHospitalStateValid = !isGmc || hospitalStateController.text.trim().isNotEmpty; + isHospitalPincodeValid = !isGmc || hospitalPinCodeController.text.trim().isNotEmpty; + isHospitalPhoneNoValid = !isGmc || hospitalPhoneNoController.text.trim().isNotEmpty; + + isAdmitDateValid = !isGmc || admitDate != null; + isDischargeDateValid = !isGmc || dischargeDate != null; + + isClaimAmountValid = !isGmc || claimAmountController.text.trim().isNotEmpty; + + // ☠ Accident / Death + final isAccident = [2, 3, 4].contains(serviceId); + + isAccidentDateValid = !isAccident || accidentDate != null; + isIntimationDateValid = !isAccident || intimationDate != null; + }); + + if (isServiceValid && + isPolicyValid && + isMemberValid && + // isSubjectValid && + isHospitalNameValid && + isHospitalAddressValid && + isHospitalStateValid && + isHospitalCityValid && + isHospitalPincodeValid && + isHospitalPhoneNoValid && + isAdmitDateValid && + isDischargeDateValid && + isClaimAmountValid && + isAccidentDateValid && + isIntimationDateValid) { + if (FileUploadService().files.isEmpty) { + setState(() { + MultiFileUploadWidget.hasFiles = false; + }); + ToastHelper.showErrorToast(context, 'Please upload at least one document'); + return; + } + // Proceed to submit + } else { + ToastHelper.showErrorToast(context, 'Please Fill Required Fields'); + return; + } + setState(() => isSubmitting = true); // 🔥 start loader + setState(() { + isLoading = true; + }); + + try { + print('Check One'); + Map fields = { + 'client_policy_id': selectedClientPolicyId, + // 'policy_type_id': selectedPolicyTypeId, + + 'emp_id': selectedMemberObject?['emp_id'], + 'ticket_type_id': serviceId, + 'subject': serviceName, + 'message': messageController.text, + + 'emp_code': selectedMemberObject?['emp_code'] ?? '', + 'mobile_number': selectedMemberObject?['mobile'] ?? ' ', + 'email': selectedMemberObject?['empEmailCorporate'] ?? '', + 'fullname': selectedMemberObject?['name'] ?? '', + 'client_id': empClientId, + 'relationship': selectedMemberObject?['relationship'], + 'gender': selectedMemberObject?['gender'], + 'age': selectedMemberObject?['dob'], + 'policy_name': serviceId == 1 + ? null + : selectedMemberObject?['policy_name'], + + }; + + + if (serviceId == 1 || serviceId == 72) { + String formattedAdmitDate = DateFormat('yyyy-MM-dd').format(admitDate!); + String formattedDischargeDate = DateFormat('yyyy-MM-dd').format(dischargeDate!); + fields['hospital_name'] = hospitalNameController.text; + fields['hospital_address'] = hospitalAddressController.text; + fields['hospital_city'] = hospitalCityController.text; + fields['hospital_state'] = hospitalStateController.text; + fields['hospital_pin_code'] = hospitalPinCodeController.text; + fields['hospital_phone_no'] = hospitalPhoneNoController.text; + fields['doa'] = formattedAdmitDate; + fields['dod'] = formattedDischargeDate; + fields['claim_amount'] = claimAmountController.text; + fields['policyholder_name'] = employeePolicyList[0]['name']; + fields['member_name'] = selectedMemberObject?['name']; + fields['insured_emp_id'] = selectedMemberObject?['insured_emp_id']; + } else { + String formattedAccidentDate = + DateFormat('yyyy-MM-dd').format(accidentDate!); + String formattedDeathDate = DateFormat('yyyy-MM-dd').format(deathDate!); + String formattedBirthDate = DateFormat('yyyy-MM-dd').format(birthDate!); + String formattedIntimationDate = + DateFormat('yyyy-MM-dd').format(intimationDate!); + fields['date_of_accident'] = formattedAccidentDate; + fields['dob'] = formattedBirthDate; + fields['date_of_intimat'] = formattedIntimationDate; + fields['date_of_death'] = formattedDeathDate; + fields['si_amt'] = sumInsuredController.text; + fields['policyholder_name'] = employeePolicyList[0]['name']; + fields['member_name'] = selectedMemberName; + fields['insured_emp_id'] = selectedMemberObject?['insured_emp_id']; + } + + + final request = http.MultipartRequest('POST', Uri.parse('${Environment.apiUrlPost}initiateClaim')); + request.headers['Authorization'] = 'Bearer $_postPreToken'; + request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; + final stringFields = fields.map((key, value) => MapEntry(key, value?.toString() ?? '')); + request.fields.addAll(stringFields); + + // get uploaded files + final uploadedFiles = FileUploadService().files; + print("📁 Total uploaded files: ${uploadedFiles.length}"); + print("📂 File list (original names): ${uploadedFiles.map((f) => f.file.name).toList()}"); + +// Validate all labels present (optional but recommended) + for (final uf in uploadedFiles) { + print("📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'"); + if ((uf.label ?? '').trim().isEmpty) { + ToastHelper.showErrorToast(context, 'Please enter name for all uploaded documents'); + setState(() => isLoading = false); + return; + } + } + + // ✅ NEW VALIDATION: At least one PDF must be uploaded + // bool hasPdf = uploadedFiles.any((uf) { + // final ext = uf.file.extension?.toLowerCase() ?? ''; + // return ext == 'pdf'; + // }); + // + // if (!hasPdf) { + // ToastHelper.showErrorToast(context, 'Please upload at least one PDF document'); + // setState(() => isLoading = false); + // return; + // } + + // Add files + // for (var uf in uploadedFiles) { + // final pf = uf.file; + // if (pf.bytes != null) { + // request.files.add(http.MultipartFile.fromBytes( + // 'claim_docs[]', + // pf.bytes!, + // filename: pf.name, + // )); + // } + // } + + // Add files — convert images to PDF if needed + for (var uf in uploadedFiles) { + final pf = uf.file; + final ext = pf.extension?.toLowerCase() ?? ''; + + if (pf.bytes != null) { + Uint8List fileBytes = pf.bytes!; + + if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) { + // ✅ Convert image → PDF + final pdf = pw.Document(); + final image = pw.MemoryImage(fileBytes); + + pdf.addPage( + pw.Page( + build: (pw.Context context) => pw.Center( + child: pw.Image(image, fit: pw.BoxFit.contain), + ), + ), + ); + + fileBytes = await pdf.save(); // converted PDF bytes + + // replace file name with .pdf extension + final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf'); + + print('📄 Converted image ${pf.name} → PDF ($pdfFileName)'); + request.files.add(http.MultipartFile.fromBytes( + 'claim_docs[]', + fileBytes, + filename: pdfFileName, + )); + } else { + // ✅ Already a PDF + request.files.add(http.MultipartFile.fromBytes( + 'claim_docs[]', + fileBytes, + filename: pf.name, + )); + } + } + } + + +// ✅ Combine all names into a JSON array string + final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList(); + final encodedNames = jsonEncode(claimDocNames); + request.fields['claim_doc_names'] = encodedNames; + +// ✅ Debug print + print("Payload being sent:"); + print("Files: ${uploadedFiles.map((f) => f.file.name).toList()}"); + print("Names (JSON): $encodedNames"); + + + final response = await request.send(); + final responseBody = await response.stream.bytesToString(); + + final decoded = jsonDecode(responseBody); + if (decoded['status'] == true) { + // 1️⃣ Close dialog first + Navigator.pop(context); + + // 2️⃣ Call parent page function + widget.onSuccess(); + + ToastHelper.showSuccessToast(context, decoded['message']); + serviceId = null; + departmentList.clear(); + setState(() { + isLoading = false; + }); + // context.go('/claims', extra: 2); + print('Form data submitted successfully'); + fileService.clearAll(); + } else { + setState(() { + isLoading = false; + }); + Navigator.pop(context); + ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}"); + } + + + } catch (e) { + setState(() { + isLoading = false; + }); + print('Error submitting form data: $e'); + } finally { + setState(() => isSubmitting = false); // 🔥 stop loader + } + } + + Future _pickDate(BuildContext context, String label) async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: selectedDates[label] ?? DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime.now(), + ); + + if (pickedDate != null && pickedDate != selectedDates[label]) { + setState(() { + selectedDates[label] = pickedDate; + }); + } + } + + Future _pickFromToDate(BuildContext context) async { + // Define the policy date range + final DateTime policyStartDate = + DateFormat('dd-MMM-yyyy').parse(rawPolicyStartDate); + final DateTime policyEndDate = + DateFormat('dd-MMM-yyyy').parse(rawPolicyEndDate); + + // Show the date range picker within the policy date limits + final DateTimeRange? pickedDateRange = await showDateRangePicker( + context: context, + firstDate: policyStartDate, + lastDate: policyEndDate, + initialDateRange: admitDate != null && dischargeDate != null + ? DateTimeRange(start: admitDate!, end: dischargeDate!) + : null, + ); + + // Update values if a valid date range is picked + if (pickedDateRange != null) { + setState(() { + admitDate = pickedDateRange.start; + dischargeDate = pickedDateRange.end; + admitDateController.text = DateFormat('dd-MM-yyyy').format(admitDate!); + dischargeDateController.text = + DateFormat('dd-MM-yyyy').format(dischargeDate!); + }); + } + } + + // Future _pickFromToDate(BuildContext context) async { + // final DateTimeRange? pickedDateRange = await showDateRangePicker( + // context: context, + // firstDate: DateTime(2000), + // lastDate: DateTime(2100), + // initialDateRange: admitDate != null && dischargeDate != null + // ? DateTimeRange(start: admitDate!, end: dischargeDate!) + // : null, + // ); + // + // if (pickedDateRange != null) { + // setState(() { + // admitDate = pickedDateRange.start; + // dischargeDate = pickedDateRange.end; + // admitDateController.text = DateFormat('yyyy-MM-dd').format(admitDate!); + // dischargeDateController.text = + // DateFormat('yyyy-MM-dd').format(dischargeDate!); + // }); + // } + // } + + int calculateAge(String dob) { + DateTime birthDate = DateTime.parse(dob); // Parse the dob string + DateTime currentDate = DateTime.now(); + + int age = currentDate.year - birthDate.year; + + // Adjust age if the birthday has not occurred yet this year + if (currentDate.month < birthDate.month || + (currentDate.month == birthDate.month && + currentDate.day < birthDate.day)) { + age--; + } + + return age; + } + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, // ✅ PURE WHITE popup + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.75, // Desktop popup width + // height: MediaQuery.of(context).size.height * 0.85, + child: Stack( + children: [ + /// MAIN CONTENT (YOUR EXISTING UI) + SingleChildScrollView( + child: Container( + padding: EdgeInsets.all(20), + color: Colors.white, + child: Column( + children: [ + /// 🔹 HEADER ROW (Title + Close) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Raise Insurance Claim", + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + + const SizedBox(height: 16), + + Form( + key: formKey, + child: Column( + children: [ + _row([ + buildDropdownField( + 'Service', + (value) { + final selectedItem = departmentList.firstWhere( + (item) => item['id'] == value, + orElse: () => {}, + ); + setState(() { + serviceId = value; + serviceName = selectedItem['name']; + policyNumberId = null; + isServiceValid = true; + }); + print('🔥 serviceId set to $serviceId'); + filterPoliciesByService(value!); + }, + departmentList, + 'name', + serviceId, + ), + buildDropdownField( + 'Select Policy', + (value) { + final selectedPolicy = + policyNumberList.firstWhere((p) => p['id'] == value); + + setState(() { + // ✅ THIS is what you send to API + selectedClientPolicyId = selectedPolicy['id']; + + // optional + selectedPolicyTypeId = selectedPolicy['policy_type_id']; + policyNumberId = value; + + isPolicyValid = true; + }); + + getCDPoliciesDetails(); + }, + policyNumberList, + 'label', // 👈 DISPLAY FIELD + policyNumberId, + ), + + buildDropdownFieldSearch( + 'Member Name', + (value) { + final member = employeePolicyList.firstWhere( + (m) => m['id'] == value, + ); + + setState(() { + selectedMemberId = value; + selectedMemberObject = member; // ✅ FULL OBJECT + selectedMemberName = member['name']; + isMemberValid = true; + print('selectedMemberObject $selectedMemberObject'); + }); + }, + employeePolicyList, + 'name', + selectedMemberId, + ), + + ]), + _row([ + buildTextField('Message', messageController), + if (serviceId == 1 || serviceId == 72)...[ + buildTextField('Hospital Name', hospitalNameController), + buildTextField('Hospital Address', hospitalAddressController), + ] + ]), + + if (serviceId == 1 || serviceId == 72) + _row([ + buildTextField('Hospital City', hospitalCityController), + buildTextField('Hospital State', hospitalStateController), + buildTextField( + 'Hospital Pincode', + hospitalPinCodeController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(6), + ], + ), + ]), + if (serviceId == 1 || serviceId == 72) + _row([ + buildTextField( + 'Hospital Phone No', + hospitalPhoneNoController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + ), + buildDatePickerField( + label: 'Admit Date', + selectedDate: admitDate, + allowFuture: false, + onDateSelected: (d) { + setState(() { + admitDate = d; + dischargeDate = null; + }); + }, + ), + buildDatePickerField( + label: 'Discharge Date', + selectedDate: dischargeDate, + allowFuture: true, + minDate: admitDate?.add(const Duration(days: 1)), + onDateSelected: (d) => setState(() => dischargeDate = d), + ), + buildTextField( + 'Claims Amount', + claimAmountController, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ), + ]), + + if ([2, 3, 4].contains(serviceId)) + _row([ + buildDatePickerField( + label: 'Date of Birth', + selectedDate: birthDate, + allowFuture: false, + onDateSelected: (d) => setState(() => birthDate = d), + ), + buildDatePickerField( + label: 'Accident Date', + selectedDate: accidentDate, + allowFuture: false, + onDateSelected: (d) { + setState(() { + accidentDate = d; + deathDate = null; + intimationDate = null; + }); + }, + ), + buildDatePickerField( + label: 'Date of Death', + selectedDate: deathDate, + allowFuture: false, + onDateSelected: (d) => setState(() => deathDate = d), + ), + ]), + if ([2, 3, 4].contains(serviceId)) + _row([ + buildDatePickerField( + label: 'Date of Intimation', + selectedDate: intimationDate, + allowFuture: false, + onDateSelected: (d) => setState(() => intimationDate = d), + ), + buildTextField( + 'Sum Insured', + sumInsuredController, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ), + const SizedBox(), + ]), + + _row([ + MultiFileUploadWidget(), + ]), + Align( + alignment: Alignment.centerRight, + child: SizedBox( + width: 120, + height: 42, + child: ElevatedButton( + onPressed: isSubmitting ? null : sendFormDataToApi, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE26728), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: isSubmitting + ? const SizedBox( + height: 18, + width: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Text( + 'Send', + style: TextStyle(color: Colors.white, + fontWeight: FontWeight.w600), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + + /// 🔄 LOADER OVERLAY (UNCHANGED) + if (isLoading) + Container( + color: const Color(0x98FFFCE5), + child: Center( + child: Image.asset( + 'assets/nhance-loader.gif', + height: 60, + width: 60, + ), + ), + ), + ], + ), + ), + ) + + ); + } + + /// ---------- HELPERS ---------- + + Widget _row(List children) { + return Padding( + padding: const EdgeInsets.only(bottom: 14), + child: Row( + children: children + .map((e) => Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 12), + child: e, + ))) + .toList(), + ), + ); + } + + Widget buildTextField( + String label, + TextEditingController controller, { + TextInputType keyboardType = TextInputType.text, + List? inputFormatters, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fieldLabel(label), + formBox( + child: TextField( + controller: controller, + keyboardType: keyboardType, + inputFormatters: inputFormatters, + decoration: const InputDecoration( + isDense: true, + border: InputBorder.none, + ), + ), + ), + ], + ); + } + + Widget buildTextAreaField(String label, TextEditingController controller) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fieldLabel(label), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF1F1F1), + borderRadius: BorderRadius.circular(8), + ), + child: TextField( + controller: controller, + maxLines: 3, + decoration: const InputDecoration( + isDense: true, + border: InputBorder.none, + ), + ), + ), + ], + ); + } + + Widget buildDropdownField( + String label, + void Function(int?) onChanged, + List> itemsList, + String displayField, + int? selectedValue, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fieldLabel(label), + formBox( + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectedValue, + hint: const Text('Select'), + icon: const Icon(Icons.keyboard_arrow_down), + items: itemsList.map>((item) { + return DropdownMenuItem( + value: item['id'], + child: Text(item[displayField]), + ); + }).toList(), + onChanged: onChanged, + ), + ), + ), + ], + ); + } + + + Widget buildDropdownFieldSearch( + String label, + void Function(int?) onChanged, + List> itemsList, + String displayField, + int? selectedValue, + ) { + final TextEditingController searchController = TextEditingController(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fieldLabel(label), + formBox( + child: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + value: selectedValue, + hint: const Text('Select'), + iconStyleData: const IconStyleData( + icon: Icon(Icons.keyboard_arrow_down), + ), + + // 🔹 SEARCH CONFIG + dropdownSearchData: DropdownSearchData( + searchController: searchController, + searchInnerWidgetHeight: 50, + searchInnerWidget: Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: searchController, + decoration: InputDecoration( + hintText: 'Search...', + 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()); + }, + ), + + // 🔹 CLEAR SEARCH WHEN CLOSED + onMenuStateChange: (isOpen) { + if (!isOpen) searchController.clear(); + }, + + items: itemsList.map((item) { + return DropdownMenuItem( + value: item['id'], + child: Text( + item[displayField], + overflow: TextOverflow.ellipsis, + ), + ); + }).toList(), + + onChanged: onChanged, + ), + ), + ), + ], + ); + } + + + Widget buildDatePickerField({ + required String label, + required DateTime? selectedDate, + required bool allowFuture, + required ValueChanged onDateSelected, + DateTime? minDate, + DateTime? maxDate, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fieldLabel(label), + formBox( + child: InkWell( + onTap: () async { + final DateTime now = DateTime.now(); + final DateTime first = minDate ?? DateTime(1980); + final DateTime last = + allowFuture ? (maxDate ?? DateTime(2100)) : now; + + final DateTime initialDate = + selectedDate ?? (first.isAfter(now) ? first : now); + + final picked = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: first, + lastDate: last, + ); + + if (picked != null) { + onDateSelected(picked); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + selectedDate != null + ? DateFormat('dd-MM-yyyy').format(selectedDate) + : 'Select', + style: const TextStyle(color: Colors.black), + ), + const Icon(Icons.calendar_today, size: 18), + ], + ), + ), + ), + ], + ); + } + + // Widget uploadBox() { + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // fieldLabel('Upload Documents'), + // Container( + // height: 120, + // width: double.infinity, + // decoration: BoxDecoration( + // color: const Color(0xFFF9F9F9), + // borderRadius: BorderRadius.circular(10), + // border: Border.all( + // color: const Color(0xFF00A6A6), + // width: 1, + // ), + // ), + // child: const MultiFileUploadWidget(), + // ), + // ], + // ); + // } + + Widget formBox({required Widget child}) { + return Container( + height: 42, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: const Color(0xFFF1F1F1), // 👈 light grey + borderRadius: BorderRadius.circular(8), + ), + child: Center(child: child), + ); + } + + Widget fieldLabel(String text) { + return Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Text( + text, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ); + } +} + +// class RaiseClaimDialog extends StatelessWidget { +// const RaiseClaimDialog({super.key}); +// +// @override +// Widget build(BuildContext context) { +// return Dialog( +// backgroundColor: Colors.white, // 👈 THIS +// insetPadding: const EdgeInsets.all(24), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(16), +// ), +// child: Container( +// width: MediaQuery.of(context).size.width * 0.75, +// padding: const EdgeInsets.all(24), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// /// HEADER +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Text( +// 'Raise Insurance Claim', +// style: GoogleFonts.poppins( +// fontSize: 20, +// fontWeight: FontWeight.w600, +// ), +// ), +// IconButton( +// icon: const Icon(Icons.close), +// onPressed: () => Navigator.pop(context), +// ), +// ], +// ), +// +// const SizedBox(height: 20), +// +// /// FORM +// Expanded( +// child: SingleChildScrollView( +// child: Column( +// children: [ +// _row([ +// _dropdown('Select Policy*'), +// _dropdown('Member Name'), +// _input('Hospital Name'), +// ]), +// _row([ +// _input('Hospital Address'), +// _input('Hospital City'), +// _input('Hospital State'), +// ]), +// _row([ +// _input('Hospital Pincode'), +// _input('Hospital Phone Number'), +// _date('Admit Date'), +// ]), +// _row([ +// _date('Discharge Date'), +// _input('Claim Amount'), +// _input('Message'), +// ]), +// +// const SizedBox(height: 20), +// +// /// UPLOAD +// _uploadBox(), +// ], +// ), +// ), +// ), +// +// const SizedBox(height: 20), +// +// /// FOOTER +// Align( +// alignment: Alignment.centerRight, +// child: SizedBox( +// width: 120, +// height: 42, +// child: ElevatedButton( +// onPressed: () {}, +// style: ElevatedButton.styleFrom( +// backgroundColor: const Color(0xFFE26728), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(10), +// ), +// ), +// child: const Text( +// 'Send', +// style: TextStyle(fontWeight: FontWeight.w600), +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ); +// } +// +// /// ---------- HELPERS ---------- +// +// Widget _row(List children) { +// return Padding( +// padding: const EdgeInsets.only(bottom: 14), +// child: Row( +// children: children +// .map((e) => Expanded(child: Padding( +// padding: const EdgeInsets.only(right: 12), +// child: e, +// ))) +// .toList(), +// ), +// ); +// } +// +// Widget _input(String label) { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text(label, style: _labelStyle), +// const SizedBox(height: 6), +// _box( +// TextField( +// decoration: const InputDecoration( +// border: InputBorder.none, +// ), +// ), +// ), +// ], +// ); +// } +// +// Widget _dropdown(String label) { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text(label, style: _labelStyle), +// const SizedBox(height: 6), +// _box( +// DropdownButtonHideUnderline( +// child: DropdownButton( +// isExpanded: true, +// hint: const Text('Select'), +// items: const [], +// onChanged: (_) {}, +// ), +// ), +// ), +// ], +// ); +// } +// +// Widget _date(String label) { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text(label, style: _labelStyle), +// const SizedBox(height: 6), +// _box( +// TextField( +// readOnly: true, +// decoration: const InputDecoration( +// suffixIcon: Icon(Icons.calendar_today, size: 18), +// border: InputBorder.none, +// ), +// ), +// ), +// ], +// ); +// } +// +// Widget _box(Widget child) { +// return Container( +// height: 42, +// padding: const EdgeInsets.symmetric(horizontal: 12), +// decoration: BoxDecoration( +// color: const Color(0xFFF1F1F1), +// borderRadius: BorderRadius.circular(8), +// ), +// child: Center(child: child), +// ); +// } +// +// Widget _uploadBox() { +// return Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text('Upload Documents', style: _labelStyle), +// const SizedBox(height: 8), +// Container( +// height: 120, +// width: double.infinity, +// decoration: BoxDecoration( +// borderRadius: BorderRadius.circular(10), +// border: Border.all( +// color: const Color(0xFF00A6A6), +// style: BorderStyle.solid, +// ), +// color: const Color(0xFFF9F9F9), +// ), +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: const [ +// Icon(Icons.insert_drive_file, +// size: 36, color: Color(0xFF00A6A6)), +// SizedBox(height: 6), +// Text( +// 'Sample_file.PDF', +// style: TextStyle(fontWeight: FontWeight.w500), +// ), +// SizedBox(height: 4), +// Text( +// '(Supported formats: PDF, PNG, JPG, JPEG, HEIC)', +// style: TextStyle(fontSize: 11, color: Colors.grey), +// ), +// ], +// ), +// ), +// ], +// ); +// } +// } + +/// ---------- STYLES ---------- +final _labelStyle = GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, +); diff --git a/lib/presentation/cdList.dart b/lib/presentation/cdList.dart new file mode 100644 index 0000000..2812e42 --- /dev/null +++ b/lib/presentation/cdList.dart @@ -0,0 +1,496 @@ +import 'dart:convert'; + +import 'package:csv/csv.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +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:universal_html/html.dart' as html; + +import 'cdTransactionDetails.dart'; +import 'package:collection/collection.dart'; + +import '../customAppBar/base_layout.dart'; +import '../service/api_service.dart'; +import '../service/token_storage_service.dart'; + + +class CdPoliciesList extends StatefulWidget { + const CdPoliciesList({Key? key}) : super(key: key); + + @override + State createState() => _CdPoliciesListState(); +} + + +class _CdPoliciesListState extends State { + final tokenService = TokenStorageService(); + Uint8List? fileBytes; + List> getCDPolicies = []; + bool isLoading = false; + bool _isLoading = false; + dynamic empClientId; + dynamic empClientBranchId; + dynamic empHrId; + String? _postPreToken = ''; + List reversedDataPolicy = []; + List> originalData = []; // Original data source + List> filteredData = []; // Filtered data source + dynamic argumentsData; + dynamic policyType; + dynamic policyName; + dynamic clientPolicyId; + dynamic clientId; + dynamic empRefId; + int inceptionType = 0; + TextEditingController searchController = TextEditingController(); + late ApiService apiService; + int _currentPage = 1; + int _rowsPerPage = 5; + + List get _paginatedData { + final startIndex = (_currentPage - 1) * _rowsPerPage; + final endIndex = + (_currentPage * _rowsPerPage).clamp(0, filteredData.length); + return filteredData.sublist(startIndex, endIndex); + } + + @override + void initState() { + super.initState(); + apiService = ApiService(context); // Initialize ApiService here + checkIds(); + } + + @override + void dispose() { + super.dispose(); + } + + Future checkIds() async { + _postPreToken = await tokenService.getCurrentToken(); + empClientId = await tokenService.readValue('empClientId'); + // empClientBranchId = await tokenService.readValue('empClientBranchId'); + empHrId = await tokenService.readValue('empHrId'); + + await getCDPoliciesDetails(empClientId, empHrId, _postPreToken); + } + + Future getCDPoliciesDetails(empClientId, empHrId, _postPreToken) async { + print('9'); + setState(() { + isLoading = true; + }); + try { + print('10'); + final response = await apiService.getCDPoliciesToApi(empClientId, empHrId, _postPreToken); + if (response['status'] == 'success') { + setState(() { + isLoading = false; + }); + setState(() { + getCDPolicies = List>.from(response['data']); + originalData = getCDPolicies; + filteredData = List.from(originalData); + print('filteredData'); + print(filteredData); + }); + } else { + setState(() { + isLoading = false; + }); + + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + void search(String query) { + print(query); + // Check if the query is empty + if (query.isEmpty) { + // If search query is empty, show all data + setState(() { + filteredData = List.from(originalData); + }); + } else { + // Filter the original data based on the search query + setState(() { + filteredData = originalData.where((row) { + // Implement your filter logic here + // For example, check if any field in the row contains the query + // Adjust this logic based on your data structure + return row['insurer_name'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['cd_master_account_no'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['balance'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()); + }).toList(); + }); + } + print(filteredData.length); + } + + void exportToCsv(List> data) { + List> rows = []; + + // Header + rows.add(['Insurer Name', 'CD Account Number', 'Current Balance']); + + // Data rows + for (var item in data) { + rows.add([ + item['insurer_name'] ?? '', + item['cd_master_account_no'] ?? '', + '₹${item['balance'] ?? '0'}', + ]); + } + + // Convert to CSV string + String csvData = const ListToCsvConverter().convert(rows); + + // For Web: Create download + final bytes = utf8.encode(csvData); + final blob = html.Blob([bytes]); + final url = html.Url.createObjectUrlFromBlob(blob); + final anchor = html.AnchorElement(href: url) + ..setAttribute("download", "CD_Policies.csv") + ..click(); + html.Url.revokeObjectUrl(url); + handleExportAction(); + } + + Future handleExportAction() async { + print('handleExportAction'); + _postPreToken = await tokenService.getCurrentToken(); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + var activity = "export_cddata"; + + print('postId - $postId'); + print('preId - $preId'); + print('activity - $activity'); + + try { + print('10'); + final response = await apiService.getPostLogHrActivity( + postId!, preId!, _postPreToken!, activity); + if (response['status'] == 'success') { + print('Request success'); + } else { + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + Future _showLogoutDialog() async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: Text("Confirm Logout"), + content: Text("Do you want to logout?"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text("Cancel"), + ), + TextButton( + onPressed: () => apiService.logout(), + child: Text("Logout"), + ), + ], + ), + ) ?? + 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, + ); + } + }, + child: _buildContent(context), + ), + ); + } + + Widget _buildContent(BuildContext context) { + return 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( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + /// 🔙 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(), + ), + const SizedBox(width: 6), + Text( + 'CD', + style: GoogleFonts.poppins( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + + /// 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), + ), + ), + ), + + 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), + isLoading + ? Expanded( + // color: Color(0x98FFFCE5), // semi-transparent overlay + child: Center( + child: Image.asset( + 'assets/nhance-loader.gif', + height: 60, + width: 60, + ), + ), + ) + : Expanded( + child: _buildCDGrid(), + ) + ], + ), + ); + } + + Widget _buildCDGrid() { + if (filteredData.isEmpty) { + return const Center( + child: Text('No CD Account Mapped'), + ); + } + + return GridView.builder( + itemCount: filteredData.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // desktop + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 3.8, // 🔥 matches image + ), + itemBuilder: (context, index) { + return InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () async { + + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => cdTransactionDetails( + insurerName: filteredData[index]['insurer_name'], + cdMasterAccountNo: filteredData[index]['cd_master_account_no'], + insurerId: filteredData[index]['insurer_id'], + cd_ac_pk: filteredData[index]['cd_ac_pk'], + empClientId: empClientId, + ), + ), + ); + }, + child: _CDPolicyCard(data: filteredData[index]), + ); + + }, + ); + } + + +} + +class _CDPolicyCard extends StatelessWidget { + final Map data; + + const _CDPolicyCard({required this.data}); + + @override + Widget build(BuildContext context) { + final balance = double.tryParse(data['balance'].toString()) ?? 0; + + final Color amountColor = balance < 0 + ? Colors.red + : balance < 50000 + ? Colors.orange + : Colors.green; + + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFFEAFAFA), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFFA0D1D3)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 7, + child: Text( + data['insurer_name'] ?? '', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Color(0xFF000000) + ), + ), + ), + Expanded( + flex: 5, + child: Text( + "₹${balance.toStringAsFixed(0)}", + textAlign: TextAlign.right, + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w600, + color: amountColor, + ), + ), + ) + ], + ), + + /// Top row (amount + arrow) + Row( + 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), + ), + ), + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: const Color(0xFF009195), + borderRadius: BorderRadius.circular(6), + ), + child: const Icon( + Icons.open_in_new, + color: Colors.white, + size: 14, + ), + ) + ], + ), + ], + ), + ); + } +} + diff --git a/lib/cdTransactionDetails.dart b/lib/presentation/cdTransactionDetails.dart similarity index 62% rename from lib/cdTransactionDetails.dart rename to lib/presentation/cdTransactionDetails.dart index 8646cf6..87b297b 100755 --- a/lib/cdTransactionDetails.dart +++ b/lib/presentation/cdTransactionDetails.dart @@ -8,14 +8,17 @@ import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; +import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/service/api_service.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:universal_html/html.dart' as html; import 'package:collection/collection.dart'; +import 'package:url_launcher/url_launcher.dart'; -import 'customAppBar/customAppBar.dart'; -import 'customAppBar/customFooter.dart'; +import '../customAppBar/base_layout.dart'; +import '../customAppBar/customAppBar.dart'; +import '../customAppBar/customFooter.dart'; class cdTransactionDetails extends StatefulWidget { final String insurerName; @@ -23,7 +26,7 @@ class cdTransactionDetails extends StatefulWidget { final String insurerId; final String cd_ac_pk; final String empClientId; - final String postToken; + // final String postToken; const cdTransactionDetails( {Key? key, required this.insurerName, @@ -31,15 +34,18 @@ class cdTransactionDetails extends StatefulWidget { required this.insurerId, required this.cd_ac_pk, required this.empClientId, - required this.postToken}); + // required this.postToken + }); @override State createState() => _cdTransactionDetailsState(); } class _cdTransactionDetailsState extends State { + final tokenService = TokenStorageService(); Uint8List? fileBytes; List> getCDTransData = []; + List> getCDEndorsementData = []; bool isLoading = false; bool _isLoading = false; // dynamic clintID; @@ -47,7 +53,9 @@ class _cdTransactionDetailsState extends State { // List dataPolicy = []; List reversedDataPolicy = []; List> originalData = []; // Original data source + List> originalEndorsementData = []; // Original data source List> filteredData = []; // Filtered data source + List> filteredEndorsementData = []; // Filtered data source List> getCDTransDataAmount = []; // Filtered data source dynamic argumentsData; dynamic policyType; @@ -84,6 +92,9 @@ class _cdTransactionDetailsState extends State { super.dispose(); } + // downloadPolicyFiles?file_id=13 + // getPolicyAndEndorsementFiles?cd_ac_pk=12 + Future getCdTransactionDetails() async { print('9'); setState(() { @@ -91,8 +102,9 @@ class _cdTransactionDetailsState extends State { }); try { print('10'); + final _postPreToken = await tokenService.getCurrentToken(); final response = await apiService.getCdTransactionData(widget.empClientId, - widget.insurerId, widget.cd_ac_pk, widget.postToken); + widget.insurerId, widget.cd_ac_pk, _postPreToken!); if (response['status'] == 'success') { setState(() { isLoading = false; @@ -130,6 +142,148 @@ class _cdTransactionDetailsState extends State { } } + Future _openEndorsementFile(id) async { + print('9'); + try { + print('10'); + final _postPreToken = await tokenService.getCurrentToken(); + final response = await apiService.getOpenEndorsementFileData(id, _postPreToken!); + if (response['status'] == false) { + ToastHelper.showErrorToast(context, response['message']); + } else { + + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + Future getCdEndorsementDetails(id) async { + print('9'); + setState(() { + isLoading = true; + }); + try { + print('10'); + final _postPreToken = await tokenService.getCurrentToken(); + final response = await apiService.getCdEndorsementData(id, _postPreToken!); + if (response['status'] == true) { + setState(() { + isLoading = false; + }); + setState(() { + getCDEndorsementData = List>.from(response['data']); + originalEndorsementData = getCDEndorsementData; + filteredEndorsementData = List.from(originalEndorsementData); + _showFileListPopup(filteredEndorsementData); + print('filteredData'); + print(filteredData); + }); + } else { + setState(() { + isLoading = false; + }); + getCDEndorsementData = List>.from(response['data']); + if (getCDEndorsementData == null || getCDEndorsementData.isEmpty || getCDEndorsementData == null || (getCDEndorsementData as List).isEmpty) { + _showEmptyPopup(response['message']); + return; + } + + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + void _showFileListPopup(List> files) { + showDialog( + context: context, + barrierDismissible: true, + builder: (_) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: const Text( + 'Files', + style: TextStyle(fontWeight: FontWeight.w600), + ), + content: SizedBox( + width: 400, + child: ListView.separated( + shrinkWrap: true, + itemCount: files.length, + separatorBuilder: (_, __) => const Divider(), + itemBuilder: (context, index) { + final file = files[index]; + + return ListTile( + leading: const Icon( + Icons.picture_as_pdf_outlined, + color: Color(0xFF00999E), + ), + title: Text( + file['file_name'] ?? 'Document', + style: const TextStyle(fontSize: 14), + ), + onTap: () { + // Navigator.pop(context); // close popup + _openEndorsementFile(file['id']); + }, + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ], + ); + }, + ); + } + + void _showEmptyPopup(String message) { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: const Text( + 'Message', + style: TextStyle(fontWeight: FontWeight.w600), + ), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ); + }, + ); + } + + void search(String query) { print(query); // Check if the query is empty @@ -230,7 +384,7 @@ class _cdTransactionDetailsState extends State { ? '${item['policy_type']} - ${item['policy_no']}' : '-', item['endorsement_no'] ?? '', - item['sub_type'] ?? '', + item['sub_type_text'] ?? '', item['transaction_type'] == 'Credit' ? '₹${item['amount']}' : '-', item['transaction_type'] == 'Debit' ? '₹${item['amount']}' : '-', '₹${item['balance'] ?? '0'}', @@ -256,9 +410,9 @@ class _cdTransactionDetailsState extends State { Future handleExportAction() async { print('handleExportAction'); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final postId = prefs.getString('empHrId'); - final preId = prefs.getString('enrollmentEmpPrimaryId'); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + var activity = "export_cdsummary"; print('postId - $postId'); @@ -267,8 +421,9 @@ class _cdTransactionDetailsState extends State { try { print('10'); + final _postPreToken = await tokenService.getCurrentToken(); final response = await apiService.getPostLogHrActivity( - postId!, preId!, widget.postToken, activity); + postId!, preId!, _postPreToken!, activity); if (response['status'] == 'success') { print('Request success'); } else { @@ -305,182 +460,202 @@ class _cdTransactionDetailsState extends State { } } + Future _launchURL(String url) async { + final Uri uri = Uri.parse(url); // Parse the URL properly + print('_launchURL $uri'); + if (uri != null) { + print('If $uri'); + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + ToastHelper.showWarningToast(context, 'File not generated'); + print('else $uri'); + throw 'Could not launch $url'; + } + } + + 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; + } + @override Widget build(BuildContext context) { - // TODO: implement build - return Scaffold( - appBar: CustomAppBar(), - backgroundColor: Color(0xFFEFF3F6), - body: Stack(children: [ - SingleChildScrollView( - child: Container( - padding: EdgeInsets.only( - top: 30, bottom: 200, left: 50, right: 50), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + 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, + ); + } + }, + child: _buildContent(context), + ), + ); + } + + 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( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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), + Text( + 'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})', + style: GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + + /// 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), + ), + ), + ), + + 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: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoCard('Deposits', '₹$total_deposit', + Color(0xFF39D45B), Icons.arrow_upward), + _buildInfoCard('Consumed', '₹$total_consumed', + Color(0xFFED1D24), Icons.arrow_downward), + _buildInfoCard('Refund', '₹$total_refund', + Color(0xFF39D45B), Icons.arrow_upward), + _buildInfoCard('Current Balance', '₹$currect_balance', + Colors.black, null), + ], + ), + 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: [ - MouseRegion( - cursor: SystemMouseCursors - .click, // 👈 shows pointer on hover - child: GestureDetector( - onTap: () { - Navigator.pop(context); // or your desired action - }, - child: Row( - children: [ - Icon(Icons.arrow_back_ios_new_outlined, - color: Color(0xFF707070)), - SizedBox(width: 8), - Expanded( - child: Text( - 'Transaction Details - ${widget.insurerName} (${widget.cdMasterAccountNo})', - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF101010), - ), - ), - ), - ], - ), - ), - ), - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildInfoCard('Deposits', '₹$total_deposit', - Color(0xFF39D45B), Icons.arrow_upward), - _buildInfoCard('Consumed', '₹$total_consumed', - Color(0xFFED1D24), Icons.arrow_downward), - _buildInfoCard('Refund', '₹$total_refund', - Color(0xFF39D45B), Icons.arrow_upward), - _buildInfoCard('Current Balance', '₹$currect_balance', - Colors.black, null), - ], - ), - 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( - flex: 4, - child: Align( - alignment: Alignment.centerLeft, - child: Container( - width: 400, - height: 37, - decoration: BoxDecoration( - color: Color(0xFFF0F0F0), - borderRadius: BorderRadius.circular(8), - ), - child: TextField( - decoration: InputDecoration( - hintText: 'Search', - prefixIcon: - Icon(Icons.search, size: 18), - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 8), - border: InputBorder - .none, // No border since Container handles it - ), - controller: searchController, - onChanged: search, - style: - GoogleFonts.poppins(fontSize: 14), - ), - ), - ), - ), - SizedBox(width: 12), - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - width: 116, - height: 37, - child: ElevatedButton( - onPressed: () { - exportToCsv(filteredData); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - padding: EdgeInsets.all( - 10), // Internal padding - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 10), // Border radius - side: BorderSide( - color: Colors - .transparent, // Optional border color - width: 1, // Border width - ), - ), - elevation: 0, - ), - child: Text( - 'Export', - style: GoogleFonts.poppins( - fontSize: 14, - color: Color(0xFFFFFFFF), - fontWeight: FontWeight.w700, - letterSpacing: 1), - ), - ), - ), - ], - ), - ), - ], - ), - SizedBox(height: 20), - Row( - children: [ - Expanded( - child: SingleChildScrollView( - child: _buildCDDataTable(context), - ), - ) - ], - ), - ], + Expanded( + child: SingleChildScrollView( + child: _buildCDDataTable(context), ), ) ], - ))), - if (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 + ), + ], ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double.infinity, // Make the footer full width - child: CustomFooter(), - ), - ), - ])); + ) + ], + )); + } + + Widget _buildInfoCard( String label, String value, Color iconColor, IconData? icon) { return Expanded( @@ -548,7 +723,7 @@ class _cdTransactionDetailsState extends State { // Header row Container( decoration: BoxDecoration( - color: Color(0xFFD7E9EB), + color: Color(0xFFD7E9EB), borderRadius: BorderRadius.circular(6), ), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), @@ -675,6 +850,19 @@ class _cdTransactionDetailsState extends State { fontWeight: FontWeight.bold), ), ), + Expanded( + flex: 2, + child: Text( + 'Action', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 12, + color: const Color(0xFF000000), + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ), @@ -688,11 +876,11 @@ class _cdTransactionDetailsState extends State { 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), + // color: Colors.white, + // borderRadius: BorderRadius.circular(6), border: Border( bottom: BorderSide( - color: Color(0xFFD7E9EB), // 👈 Bottom border color + color: Color(0xFFA9D9DE), // 👈 Bottom border color width: 1, // 👈 Optional: thickness ), ), @@ -765,7 +953,7 @@ class _cdTransactionDetailsState extends State { Expanded( flex: 2, child: Text( - item['sub_type'] ?? '-', + item['sub_type_text'] ?? '-', textAlign: TextAlign.left, style: GoogleFonts.poppins( color: Color(0xFF000000), @@ -832,6 +1020,35 @@ class _cdTransactionDetailsState extends State { ), ), ), + 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'] + ), + ], + ), + ), + ], ), ); @@ -917,6 +1134,41 @@ class _cdTransactionDetailsState extends State { } } +class _ActionIconButton extends StatelessWidget { + final IconData icon; + final VoidCallback onTap; + final String? subType; + + + const _ActionIconButton({ + required this.icon, + required this.onTap, + required this.subType, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 36, + height: 36, + child: Material( + color: const Color(0xFFDFF4F5), // light teal bg + borderRadius: BorderRadius.circular(10), + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: (subType == '3' || subType == '4') ? onTap : null, + child: Icon( + icon, + size: 22, + color: Colors.black, + ), + ), + ), + ); + } +} + + // Sample Data class representing each element in the array class Data { final dynamic value; diff --git a/lib/service/hrDashboardTabs/claims.dart b/lib/presentation/claims.dart similarity index 70% rename from lib/service/hrDashboardTabs/claims.dart rename to lib/presentation/claims.dart index af237cd..00999f6 100755 --- a/lib/service/hrDashboardTabs/claims.dart +++ b/lib/presentation/claims.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; + +import 'package:csv/csv.dart'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; @@ -7,46 +10,31 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:collection/collection.dart'; import 'package:jwt_decode/jwt_decode.dart'; +import 'package:universal_html/html.dart' as html; -import '../../claimshistory.dart'; -import '../api_service.dart'; +import 'claimshistory.dart'; +import '../customAppBar/base_layout.dart'; +import '../service/api_service.dart'; +import '../service/token_storage_service.dart'; +import 'RaiseClaimForm.dart'; class ClaimsPolicies extends StatefulWidget { - final String empClientId; - final String empClientBranchId; - final String empHrId; - final String postToken; - final String empCodeHrPolicy; - - final int isHrcode; - - const ClaimsPolicies( - {Key? key, - required this.empClientId, - required this.empClientBranchId, - required this.empHrId, - required this.postToken, - required this.empCodeHrPolicy, - required this.isHrcode}); + const ClaimsPolicies({Key? key}) : super(key: key); @override State createState() => _ClaimsPolicieState(); } -class _ClaimsPolicieState extends State { - List tabHeader = ['name', 'empCode', 'from', 'to', 'claimNum']; +class _ClaimsPolicieState extends State { + List tabHeader = ['name', 'empCode', 'from', 'to', 'claimNum','policyNumber']; + final tokenService = TokenStorageService(); List> filteredData = []; // Filtered data source - // List> filteredData = [ - // { - // "client_id": "58", - // "insurer_id": "2", - // "cd_ac_pk": "94", - // "insurer_name": "ICICI Prudential Life Insurance Co. Ltd", - // "cd_master_account_no": "CD-IOCL-887744559966", - // "balance": "10000000.00" - // } - // ]; + dynamic empClientId; + dynamic empClientBranchId; + dynamic empHrId; + String? _postPreToken = ''; + TextEditingController searchController = TextEditingController(); Map controllers = {}; List reversedDataPolicy = []; @@ -56,7 +44,6 @@ class _ClaimsPolicieState extends State { int? selectedPolicyType; int? selectedClaimStatus; - dynamic empClientId; List> getClaimPolicies = []; Map getClaimPoliciesApi = {}; bool isLoading = false; @@ -71,16 +58,41 @@ class _ClaimsPolicieState extends State { return filteredData.sublist(startIndex, endIndex); } + Color getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'claim received': + return const Color(0xFF4A90E2); // blue + case 'under process': + return const Color(0xFFF5A623); // orange + case 'information required': + return const Color(0xFFFF8C42); // amber + case 'approved': + return const Color(0xFF2ECC71); // green + case 'settled': + return const Color(0xFF1ABC9C); // teal-green + case 'rejected': + return const Color(0xFFE74C3C); // red + case 'denial review awaite': + return const Color(0xFF9B59B6); // purple + case 'closed': + return const Color(0xFFB0BEC5); // purple + default: + return const Color(0xFFB0BEC5); // grey fallback + } + } + + Map claim_Detials() { final data = { - "client_id": widget.empClientId, - "emp_code": controllers["empCode"]?.text ?? '', + "client_id": empClientId ?? '', + "client_branch_id": empClientBranchId ?? '', + // "emp_code": controllers["empCode"]?.text ?? '', "from_date": controllers["from"]?.text ?? '', "to_date": controllers["to"]?.text ?? '', - "ticket_type_id": selectedPolicyType ?? '', + // "ticket_type_id": selectedPolicyType ?? '', "claim_status_id": selectedClaimStatus ?? '', - "claim_number": controllers["claimNum"]?.text ?? '', - "emp_name": controllers["name"]?.text ?? '', + "policy_no": controllers["policyNumber"]?.text ?? '', + // "emp_name": controllers["name"]?.text ?? '', }; return data; } @@ -94,43 +106,47 @@ class _ClaimsPolicieState extends State { controllers[field] = TextEditingController(); } - print("TextCXOnte - $controllers"); - if (widget.postToken != null && widget.postToken.isNotEmpty) { - Map? postdecodedToken = Jwt.parseJwt(widget.postToken); + // if (_postPreToken != null && _postPreToken!.isNotEmpty) { + // + // claim_Detials(); + // + // print("Empcoed1- ${widget.empCodeHrPolicy}"); + // print("EmpisHrcode- ${empHrId}"); + // if (widget.empCodeHrPolicy != '') { + // print("Empcoedbool- ${widget.isHrcode}"); + // controllers['empCode']!.text = widget.empCodeHrPolicy; + // // if (widget.isHrcode == 0) { + // // controllers['empCode']?.clear(); + // // } else { + // // controllers['empCode']!.text = widget.empCodeHrPolicy; + // // } + // + // print("Empcoed- ${widget.empCodeHrPolicy}"); + // } + // + // // if (widget.empCodeHrPolicy != '' && widget.isHrcode) { + // // print("Empcoedbool- ${widget.isHrcode}"); + // // controllers['empCode']!.text = widget.empCodeHrPolicy; + // // print("Empcoed- ${widget.empCodeHrPolicy}"); + // // } + // + // getClaimList(); + // } - empClientId = postdecodedToken['client_id'].toString(); - print("empClientId - $empClientId"); - claim_Detials(); - - print("Empcoed1- ${widget.empCodeHrPolicy}"); - print("EmpisHrcode- ${widget.isHrcode}"); - if (widget.empCodeHrPolicy != '') { - print("Empcoedbool- ${widget.isHrcode}"); - controllers['empCode']!.text = widget.empCodeHrPolicy; - // if (widget.isHrcode == 0) { - // controllers['empCode']?.clear(); - // } else { - // controllers['empCode']!.text = widget.empCodeHrPolicy; - // } - - print("Empcoed- ${widget.empCodeHrPolicy}"); - } - - // if (widget.empCodeHrPolicy != '' && widget.isHrcode) { - // print("Empcoedbool- ${widget.isHrcode}"); - // controllers['empCode']!.text = widget.empCodeHrPolicy; - // print("Empcoed- ${widget.empCodeHrPolicy}"); - // } - - getClaimList(); - } - - getClaimsPoliciesDetails(); // getApiData(); + _loadIds(); } + Future _loadIds() async { + _postPreToken = tokenService.getCurrentToken(); + empClientId = await tokenService.readValue('empClientId'); + empClientBranchId = await tokenService.readValue('empClientBranchId'); + + getClaimsPoliciesDetails(); + getClaimList(); + } @override void dispose() { for (var controller in controllers.values) { @@ -147,7 +163,7 @@ class _ClaimsPolicieState extends State { }); try { print('10'); - final response = await apiService.getClaimPoliciesToApi(widget.postToken); + final response = await apiService.getClaimPoliciesToApi(_postPreToken!); if (response['status'] == 'success') { setState(() { isLoading = false; @@ -182,12 +198,16 @@ class _ClaimsPolicieState extends State { isLoading = true; }); + /// ✅ Resolve futures FIRST + empClientId ??= await tokenService.readValue('empClientId'); + empClientBranchId ??= await tokenService.readValue('empClientBranchId'); + final requestData = claim_Detials(); print("Request Body: $requestData"); try { final response = await apiService.getClaimPoliciesListDataToApi( - widget.postToken, + _postPreToken!, requestData, ); @@ -216,14 +236,26 @@ class _ClaimsPolicieState extends State { } } - void applyFilter() { + int getStatusCount(String status) { + return filteredData + .where((item) => + (item['status'] ?? '').toString().toLowerCase() == + status.toLowerCase()) + .length; + } + + + Future applyFilter() async { print("Filtersss"); + /// ✅ Resolve futures FIRST + empClientId ??= await tokenService.readValue('empClientId'); + empClientBranchId ??= await tokenService.readValue('empClientBranchId'); final data = claim_Detials(); print("data -- $data"); getClaimList(); } - void reset() { + Future reset() async { setState(() { controllers.forEach((key, controller) { controller.clear(); @@ -238,41 +270,326 @@ class _ClaimsPolicieState extends State { getClaimList(); }); + /// ✅ Resolve futures FIRST + empClientId ??= await tokenService.readValue('empClientId'); + empClientBranchId ??= await tokenService.readValue('empClientBranchId'); + final data = claim_Detials(); print("restdata -- $data"); } + void exportToCsv(List> data) { + List> rows = []; + + // Header + rows.add(['Emp Name', 'Emp Code', 'Policy Type','Client Policy No','claim_no','status','claim_amount','ticket_created_date']); + + // Data rows + for (var item in data) { + rows.add([ + item['emp_name'] ?? '', + item['emp_code'] ?? '', + item['policy_type'] ?? '', + item['client_policy_no'] ?? '', + item['claim_no'] ?? '', + item['cl_type'] ?? '', + item['status'] ?? '', + item['claim_amount'] ?? '', + item['ticket_created_date'] ?? '', + ]); + } + + // Convert to CSV string + String csvData = const ListToCsvConverter().convert(rows); + + // For Web: Create download + final bytes = utf8.encode(csvData); + final blob = html.Blob([bytes]); + final url = html.Url.createObjectUrlFromBlob(blob); + final anchor = html.AnchorElement(href: url) + ..setAttribute("download", "Claims.csv") + ..click(); + html.Url.revokeObjectUrl(url); + handleExportAction(); + } + + Future handleExportAction() async { + print('handleExportAction'); + _postPreToken = await tokenService.getCurrentToken(); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + var activity = "export_cddata"; + + print('postId - $postId'); + print('preId - $preId'); + print('activity - $activity'); + + try { + print('10'); + final response = await apiService.getPostLogHrActivity( + postId!, preId!, _postPreToken!, activity); + if (response['status'] == 'success') { + print('Request success'); + } else { + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + void search(String query) { + print(query); + // Check if the query is empty + if (query.isEmpty) { + // If search query is empty, show all data + setState(() { + filteredData = List.from(originalData); + }); + } else { + // Filter the original data based on the search query + setState(() { + filteredData = originalData.where((row) { + // Implement your filter logic here + // For example, check if any field in the row contains the query + // Adjust this logic based on your data structure + return row['emp_name'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['emp_code'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['policy_type'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['client_policy_no'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['claim_no'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['cl_type'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['status'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['claim_amount'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['ticket_created_date'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()); + }).toList(); + }); + } + print(filteredData.length); + } + + 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; + } + @override Widget build(BuildContext context) { - // TODO: implement build + 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, + ); + } + }, + child: _buildContent(context), + ), + ); + } + + Widget _buildContent(BuildContext context) { return Container( decoration: BoxDecoration( // color: Colors.red.shade50, - color: Colors.white, + // color: Colors.white, borderRadius: BorderRadius.circular(10), // 👈 set your desired radius ), padding: const EdgeInsets.all(16.0), child: Column( children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + /// 🔙 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(), + ), + const SizedBox(width: 6), + Text( + 'Claims', + style: GoogleFonts.poppins( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + + /// 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), + ), + ), + ), + + 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, + ), + ), + ), + ), + const SizedBox(width: 12), + /// ⬇️ Add Button + SizedBox( + width: 100, + height: 37, + child: ElevatedButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => RaiseClaimDialog( + parentContext: context, // 👈 pass parent context + onSuccess: _loadIds, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF00999E), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Text( + 'Add', + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.white, + letterSpacing: 1, + ), + ), + ), + ), + ], + ), + SizedBox(height: 20), _buildInputFields(context), + const SizedBox(height: 10), + _buildStatusSummary(), SizedBox( height: 10, ), isLoading ? Expanded( - // 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 - ), - ) + // 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 + ), + ) : Expanded( - child: _buildClaimsDataTable(context), - ), + child: _buildClaimsDataTable(context), + ), // Expanded( // child: Container( // child: Column( @@ -292,32 +609,25 @@ class _ClaimsPolicieState extends State { ); } + + Widget _buildInputFields(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildName(context), - _buildEmpCode(context), - _buildFrom(context), - _buildTo(context), - ], - ), - const SizedBox( - height: 10, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildPolicyType(context), - _buildClaimStatus(context), - _buildClaimNumber(context), - _buildActions(context), - ], - ) - ], + return SizedBox( + height: 70, // 🔥 controls total height like image + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _buildFrom(context), + const SizedBox(width: 12), + _buildTo(context), + const SizedBox(width: 12), + _buildPolicyNumber(context), + const SizedBox(width: 12), + _buildClaimStatus(context), + const SizedBox(width: 12), + _buildActions(context), + ], + ), ); } @@ -710,7 +1020,13 @@ class _ClaimsPolicieState extends State { ), Expanded( flex: 3, - child: Text(item['claim_no'] ?? '-', style: _dataBold), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(item['claim_no'] ?? '-', style: _dataBold), + Text(item['cl_type'] ?? '-', style: _dataColorSub), + ], + ), ), Expanded( flex: 4, @@ -720,13 +1036,12 @@ class _ClaimsPolicieState extends State { mainAxisSize: MainAxisSize.min, children: [ Container( - padding: - const EdgeInsets.symmetric(horizontal: 14, vertical: 4), + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: Color(0xFF7BD9B6), - borderRadius: BorderRadius.circular(6), + color: getStatusColor(item['status'] ?? ''), + borderRadius: BorderRadius.circular(10), ), - child: Text(item['status'] ?? '-', style: _dataBold)), + child: Text(item['status'] ?? '-', style: _dataBoldStatus)), ], ), ), @@ -769,7 +1084,7 @@ class _ClaimsPolicieState extends State { clientPolicyNo: item['client_policy_no'] ?? '', claimAmount: item['claim_amount']?.toString() ?? '', claimNo: item['claim_no'] ?? '', - postToken: widget.postToken ?? '', + postToken: _postPreToken ?? '', ), ); }, @@ -800,18 +1115,70 @@ class _ClaimsPolicieState extends State { ); } + Widget _buildStatusSummary() { + final List statusMaster = + getClaimPoliciesApi['claim_status'] ?? []; + + if (statusMaster.isEmpty) return const SizedBox(); + + return SizedBox( + height: 30, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: statusMaster.length, + separatorBuilder: (_, __) => const SizedBox(width: 10), + itemBuilder: (context, index) { + final statusName = + statusMaster[index]['claim_status']?.toString() ?? ''; + + final count = getStatusCount(statusName); + final color = getStatusColor(statusName); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '$statusName - $count', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ); + }, + ), + ); + } + + static final _dataBold = GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); + static final _dataBoldStatus = GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white, + ); + static final _dataSub = GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); + static final _dataColorSub = GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Color(0xFFFF731C), + ); + static const _headerStyle = TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -1038,149 +1405,6 @@ class _ClaimsPolicieState extends State { ); } - Widget _buildFrom(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width * 0.22, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "From", - style: TextStyle(fontSize: 12), - ), - const SizedBox( - height: 3, - ), - SizedBox( - height: 40, - child: TextField( - controller: controllers['from'], - 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), - ); - - if (pickedDate != null) { - String formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}"; - - print("FomatedFromDAta - $formattedDate"); - setState(() { - controllers['from']?.text = formattedDate; - }); - } - }, - style: const TextStyle( - fontSize: 12, - ), - decoration: InputDecoration( - floatingLabelBehavior: FloatingLabelBehavior.never, - - hintText: 'Select', - filled: true, - fillColor: Color(0xFFD5F5F6), - - border: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(8.0), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(8.0), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.teal, width: 2.0), - borderRadius: BorderRadius.circular(8.0), - ), - // contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 10), - suffixIcon: const Icon(Icons.calendar_today, size: 16), - ), - ), - ), - ], - ), - ); - } - - Widget _buildTo(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width * 0.22, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "To", - style: TextStyle(fontSize: 12), - ), - const SizedBox( - height: 3, - ), - SizedBox( - height: 40, - 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), - ); - - if (pickedDate != null) { - String formattedDate = - "${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}"; - - print("FomatedFromDAta - $formattedDate"); - setState(() { - controllers['to']?.text = formattedDate; - }); - } - }, - style: const TextStyle( - fontSize: 12, - ), - decoration: InputDecoration( - floatingLabelBehavior: FloatingLabelBehavior.never, - - hintText: 'Select', - - filled: true, - fillColor: Color(0xFFD5F5F6), - - border: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(8.0), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent), - borderRadius: BorderRadius.circular(8.0), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.teal, width: 2.0), - borderRadius: BorderRadius.circular(8.0), - ), - // contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), - contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 10), - suffixIcon: const Icon(Icons.calendar_today, size: 16), - ), - ), - ), - ], - ), - ); - } - Widget _buildPolicyType(BuildContext context) { List ticketTypeList = getClaimPoliciesApi['ticket_type'] ?? []; @@ -1295,6 +1519,175 @@ class _ClaimsPolicieState extends State { ); } + Widget _buildFrom(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width * 0.15, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "From", + 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 + ), + ], + ), + child: TextField( + controller: controllers['from'], + 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), + ); + + if (pickedDate != null) { + String formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}"; + + print("FomatedFromDAta - $formattedDate"); + setState(() { + controllers['from']?.text = formattedDate; + }); + } + }, + style: const TextStyle( + fontSize: 12, + ), + decoration: InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.never, + + hintText: 'Select', + filled: true, + fillColor: Colors.white, + + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(8.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(8.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.teal, width: 2.0), + borderRadius: BorderRadius.circular(8.0), + ), + // contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 10), + suffixIcon: const Icon(Icons.calendar_today, size: 16), + ), + ), + ) + ), + ], + ), + ); + } + + Widget _buildTo(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width * 0.15, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "To", + 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 + ), + ], + ), + 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), + ); + + if (pickedDate != null) { + String formattedDate = + "${pickedDate.day.toString().padLeft(2, '0')}-${pickedDate.month.toString().padLeft(2, '0')}-${pickedDate.year}"; + + print("FomatedFromDAta - $formattedDate"); + setState(() { + controllers['to']?.text = formattedDate; + }); + } + }, + style: const TextStyle( + fontSize: 12, + ), + decoration: InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.never, + + hintText: 'Select', + + filled: true, + fillColor: Colors.white, + + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(8.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.circular(8.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.teal, width: 2.0), + borderRadius: BorderRadius.circular(8.0), + ), + // contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 10), + suffixIcon: const Icon(Icons.calendar_today, size: 16), + ), + ), + ) + ), + ], + ), + ); + } + Widget _buildClaimStatus(BuildContext context) { List claimStatusList = getClaimPoliciesApi['claim_status'] ?? []; @@ -1332,7 +1725,7 @@ class _ClaimsPolicieState extends State { } return Container( - width: MediaQuery.of(context).size.width * 0.22, + width: MediaQuery.of(context).size.width * 0.15, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1345,6 +1738,18 @@ class _ClaimsPolicieState extends State { ), 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 + ), + ], + ), child: DropdownSearch( selectedItem: selectedClaimStatusName, items: (String? filter, _) { @@ -1374,7 +1779,7 @@ class _ClaimsPolicieState extends State { hintText: "Select Policy Type", hintStyle: TextStyle(fontSize: 12), filled: true, - fillColor: Color(0xFFD5F5F6), + fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, @@ -1408,20 +1813,21 @@ class _ClaimsPolicieState extends State { ), ), ), + ) ), ], ), ); } - Widget _buildClaimNumber(BuildContext context) { + Widget _buildPolicyNumber(BuildContext context) { return Container( - width: MediaQuery.of(context).size.width * 0.22, + width: MediaQuery.of(context).size.width * 0.15, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Claim Number", + "Policy Number", style: TextStyle(fontSize: 12), ), const SizedBox( @@ -1429,18 +1835,30 @@ class _ClaimsPolicieState extends State { ), SizedBox( height: 40, - child: TextField( - controller: controllers['claimNum'], + 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 + ), + ], + ), + child: TextField( + controller: controllers['policyNumber'], style: const TextStyle( fontSize: 12, ), decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.never, - hintText: 'Enter Claim Number', + hintText: 'Policy Number', filled: true, - fillColor: Color(0xFFD5F5F6), + fillColor: Colors.white, border: OutlineInputBorder( borderSide: BorderSide(color: Colors.transparent), @@ -1458,6 +1876,7 @@ class _ClaimsPolicieState extends State { contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 10), ), ), + ) ), ], ), @@ -1465,64 +1884,66 @@ class _ClaimsPolicieState extends State { } Widget _buildActions(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width * 0.22, - height: 50, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, + return SizedBox( + width: 100, // enough for 2 icons + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: () { - applyFilter(); - }, - icon: const Icon(Icons.filter_alt_outlined, - color: Colors.white, size: 18), - label: const Text( - 'Filter', - style: TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.w500), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - padding: EdgeInsets.symmetric(horizontal: 4, vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), + /// 🔹 Empty label space (matches "From / To / Policy Number") + const SizedBox( + height: 20, // same height as label text + spacing ), + + /// 🔹 Icon row aligned with input fields SizedBox( - width: 10, - ), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - reset(); - }, - icon: const Icon(Icons.refresh_outlined, - color: Colors.white, size: 18), - label: const Text( - 'Reset', - style: TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.w500), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - padding: EdgeInsets.symmetric(horizontal: 4, vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + height: 40, // EXACT same height as input fields + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _IconActionButton( + icon: Icons.filter_alt_outlined, + onTap: applyFilter, ), - ), + const SizedBox(width: 12), + _IconActionButton( + icon: Icons.refresh_outlined, + onTap: reset, + ), + ], ), ), ], ), ); } + +} + +// ================= ICON BUTTON ================= +class _IconActionButton extends StatelessWidget { + final IconData icon; + final VoidCallback onTap; + + const _IconActionButton({ + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 40, + height: 40, + child: Material( + color: const Color(0xFF00999E), + borderRadius: BorderRadius.circular(10), + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: onTap, + child: Icon(icon, color: Colors.white, size: 20), + ), + ), + ); + } } diff --git a/lib/presentation/claimshistory.dart b/lib/presentation/claimshistory.dart new file mode 100755 index 0000000..6038e09 --- /dev/null +++ b/lib/presentation/claimshistory.dart @@ -0,0 +1,1590 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:nhancepolicy/responsive.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/file_upload_service.dart'; +import 'package:nhancepolicy/service/multi_file_upload_widget.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:pdf/widgets.dart' as pw; +import '../../../config/environment.dart'; +import '../../../customAppBar/toastHelper.dart'; + +class ClaimHistoryPopup extends StatefulWidget { + final String ticket_id; + final String empName; + final String empCode; + final String policyType; + final String? clientPolicyNo; + final String? claimAmount; + final String? claimNo; + final String postToken; + + const ClaimHistoryPopup({ + Key? key, + required this.ticket_id, + required this.empName, + required this.empCode, + required this.policyType, + this.clientPolicyNo, + this.claimAmount, + this.claimNo, + required this.postToken, + }) : super(key: key); + + @override + State createState() => _ClaimHistoryPopupState(); +} + +class _ClaimHistoryPopupState extends State + with TickerProviderStateMixin { + late ApiService apiService; + List> getClaimsHistoryList = []; + List stepKeys = []; + late Map stepMap; + bool isLoading = false; + dynamic _token; + + String? selectedFileNames; + // html.File? uploadedFile; + // List uploadedFiles = []; + List uploadedFiles = []; + final FileUploadService fileService = FileUploadService(); + + List claimFiles = []; + List> requiredDocsList = []; + List> requiredDocsListBackup = []; + bool isSubmitting = false; + + bool isActionFreeze = false; + + // IR Docs state + bool showIRDocs = false; + // final List> irDocList = [ + // {"title": "Hospital Bill", "checked": false}, + // {"title": "Discharge Summary", "checked": false}, + // {"title": "Prescription", "checked": false}, + // ]; + final Map _assignedFiles = {}; + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + + // debug prints kept + print("CLAIMHISTORY"); + print(widget.claimAmount); + print(widget.claimNo); + print(widget.clientPolicyNo); + print(widget.empCode); + print(widget.policyType); + print(widget.ticket_id); + getClaimsHistoryDetails(); + } + + @override + void dispose() { + super.dispose(); + } + + // ------------------------- + // File pick (web + mobile) + // ------------------------- + Future pickFile() async { + final result = await FilePicker.platform + .pickFiles(withData: true, allowMultiple: false); + if (result != null && result.files.isNotEmpty) { + return result.files.first; + } + return null; + } + + // ------------------------- + // Assign same picked file to all checked docs + // ------------------------- + Future handleUploadForSelected() async { + final checked = + requiredDocsList.where((d) => d['document_received'] == true).toList(); + if (checked.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please select at least one document"))); + return; + } + + final picked = await pickFile(); + if (picked == null) return; + + setState(() { + for (var d in checked) { + final title = d['document_name'] as String; + _assignedFiles[title] = picked; + } + }); + } + + // ------------------------- + // Remove assignment + // ------------------------- + void removeAssignedFile(String title) { + setState(() { + _assignedFiles[title] = null; + }); + } + + // ------------------------- + // Submit IR Docs (placeholder) + // Replace with real API upload logic + // ------------------------- + Future submitIRDocs() async { + setState(() => isSubmitting = true); // 🔥 start loader + + try { + print('enter'); + + // STEP 1: Must select at least one document type + final selectedDocs = requiredDocsList + .where((d) => d['document_received'] == true) + .toList(); + if (selectedDocs.isEmpty) { + ToastHelper.showErrorToast( + context, "Please select at least one IR document type"); + return; + } + + // STEP 2: Must upload at least one file + if (!MultiFileUploadWidget.hasFiles || fileService.files.isEmpty) { + ToastHelper.showErrorToast( + context, "Please upload at least one document"); + return; + } + + // STEP 3: All uploaded files must have a label + for (final uf in fileService.files) { + if ((uf.label ?? "").trim().isEmpty) { + ToastHelper.showErrorToast( + context, "Please enter name for all uploaded documents"); + return; + } + } + + // STEP 4: Prepare multipart request + final url = Uri.parse("${Environment.apiUrlPost}uploadIRDocs"); + final request = http.MultipartRequest('POST', url); + + request.headers['Authorization'] = "Bearer ${widget.postToken}"; + request.headers['APP-SIGNATURE'] = + 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; + + final requiredDocsPayload = { + "is_action_freeze": isActionFreeze, + "docs": requiredDocsList.map((d) { + return { + "document_name": d["document_name"], + "document_received": d["document_received"], + }; + }).toList(), + }; + + request.fields["required_docs"] = jsonEncode(requiredDocsPayload); + + // Add mapped fields if required + request.fields['ticket_id'] = widget.ticket_id; + + // STEP 5: Convert image → PDF and attach files + for (var uf in fileService.files) { + final pf = uf.file; + final ext = pf.extension?.toLowerCase() ?? ""; + + Uint8List fileBytes = pf.bytes!; + + // if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) { + // final pdf = pw.Document(); + // final image = pw.MemoryImage(fileBytes); + // + // pdf.addPage( + // pw.Page( + // build: (context) => pw.Center(child: pw.Image(image)), + // ), + // ); + // + // fileBytes = await pdf.save(); + // + // final pdfName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf'); + // + // request.files.add(http.MultipartFile.fromBytes( + // "claim_docs[]", + // fileBytes, + // filename: pdfName, + // )); + // } else { + request.files.add(http.MultipartFile.fromBytes( + "claim_docs[]", + fileBytes, + filename: pf.name, + )); + // } + } + + // STEP 6: Add names for these IR docs + final labels = fileService.files.map((f) => f.label.trim()).toList(); + request.fields['claim_doc_names'] = jsonEncode(labels); + + // Debug + print( + "Files uploaded: ${fileService.files.map((e) => e.file.name).toList()}"); + print("Labels: $labels"); + + // STEP 7: Send the request + final response = await request.send(); + final responseBody = await response.stream.bytesToString(); + final decoded = jsonDecode(responseBody); + + if (decoded["status"] == true) { + ToastHelper.showSuccessToast(context, decoded["message"]); + + _resetIRDocs(); + await getClaimsHistoryDetails(); // <--- refresh checkbox state from API + setState(() => showIRDocs = false); + } else { + ToastHelper.showErrorToast( + context, "Upload failed: ${decoded['message']}"); + } + } catch (e) { + ToastHelper.showErrorToast(context, "Upload failed"); + } finally { + setState(() => isSubmitting = false); // 🔥 stop loader + } + } + + // ------------------------- + // Existing API call (unchanged) + // ------------------------- + Future getClaimsHistoryDetails() async { + setState(() { + isLoading = true; + }); + try { + final response = await apiService.getClaimsHistoryToApi( + widget.ticket_id, widget.postToken); + if (response['status'] == 'success') { + setState(() { + isLoading = false; + final claimsDocs = response['data']['claim_files']; + claimFiles = List>.from(claimsDocs); + + getClaimsHistoryList = [ + Map.from(response['data']['ticket_data']) + ]; + stepMap = getClaimsHistoryList[0]; + stepKeys = stepMap.keys.toList(); + + final isFreeze = + response['data']['required_docs']['is_action_freeze'] ?? false; + setState(() { + isActionFreeze = isFreeze; + }); + final requiredDocs = response['data']['required_docs']['docs']; + requiredDocsList = List>.from(requiredDocs); + +// ⭐ Make a backup copy to restore later + requiredDocsListBackup = requiredDocsList + .map((doc) => { + "document_name": doc["document_name"], + "document_received": doc["document_received"], + }) + .toList(); + + print(isActionFreeze); + print('requiredDocsList $requiredDocsList'); + + for (var d in requiredDocsList) { + _assignedFiles[d['document_name']] = null; + } + }); + } else { + setState(() => isLoading = false); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() => isLoading = false); + print('Exception occurred: $e'); + } + } + + Future _launchURL(String url) async { + final Uri uri = Uri.parse(url); + try { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (e) { + print('Could not launch URL: $e'); + } + } + + void _resetIRDocs() { + // Restore checkbox values from API backup + requiredDocsList = requiredDocsListBackup + .map((doc) => { + "document_name": doc["document_name"], + "document_received": doc["document_received"], + }) + .toList(); + + // Clear assigned uploaded files + _assignedFiles.updateAll((key, value) => null); + + // Reset multi-file upload widget service + fileService.clearAll(); + MultiFileUploadWidget.hasFiles = false; + + setState(() {}); + } + + // ------------------------- + // Build + // ------------------------- + @override + Widget build(BuildContext context) { + // responsive decisions + final mediaW = MediaQuery.of(context).size.width; + final isMobile = mediaW < 600; + final panelWidth = + isMobile ? MediaQuery.of(context).size.width * 0.95 : 400.0; + + final keyValueWidgets = [ + _buildKeyValue( + 'Name', '${widget.empName ?? ''} (${widget.empCode ?? ''})'), + SizedBox( + width: Responsive.isDesktop(context) ? 16 : 0, + height: Responsive.isDesktop(context) ? 0 : 8), + _buildKeyValue('Policy Name', + '${widget.policyType ?? ''} - ${widget.clientPolicyNo ?? ''}'), + ]; + + final keyValueWidgetRow = [ + _buildKeyValue( + 'Claims Amount', + widget.claimAmount == null || widget.claimAmount!.trim().isEmpty + ? 'N/A' + : '₹${widget.claimAmount}'), + SizedBox( + width: Responsive.isDesktop(context) ? 16 : 0, + height: Responsive.isDesktop(context) ? 0 : 8), + _buildKeyValue( + 'Claims Number', + widget.claimNo == null || widget.claimNo!.trim().isEmpty + ? 'N/A' + : widget.claimNo!), + ]; + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + Navigator.pop(context); + }, + child: Container( + // keep popup sized; adjust if required + constraints: BoxConstraints(maxWidth: 1080, maxHeight: 820), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header row (keeps IR docs icon + close inside popup header) + Row( + children: [ + Expanded( + child: Text( + 'Claims History', + style: GoogleFonts.poppins( + fontSize: Responsive.isDesktop(context) ? 20 : 16, + fontWeight: FontWeight.w500, + color: Color(0xFF101010), + ), + ), + ), + + // IR Docs icon + if (!isActionFreeze && requiredDocsList.isNotEmpty) + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + setState(() { + showIRDocs = true; + }); + }, + child: Container( + height: 34, + width: 34, + margin: EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color(0xFFBCBCBC)), + borderRadius: BorderRadius.circular(6), + ), + child: Icon(Icons.folder_open, + size: 20, color: Color(0xFF00A5A8)), + ), + ), + ), + + // Close popup + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color(0xFFBCBCBC)), + borderRadius: BorderRadius.circular(6), + ), + child: + Icon(Icons.close, size: 25, color: Color(0xFFBCBCBC)), + ), + ), + ) + ], + ), + + SizedBox(height: Responsive.isDesktop(context) ? 10 : 6), + + // BODY: for mobile we will show either the Claim content or full IR Docs content (switch) + // for desktop we show Row with left content and optional right panel + // Expanded( child: isMobile ? AnimatedSwitcher( duration: Duration(milliseconds: 300), transitionBuilder: (child, animation) { final offsetAnimation = Tween( begin: Offset(0, 1), end: Offset(0, 0)) .animate(animation); return SlideTransition( position: offsetAnimation, child: child); }, child: showIRDocs ? _buildMobileFullIrDocs(panelWidth, key: ValueKey('mobile_ir')) : _buildMainLeftContent(key: ValueKey('main_left')), ) : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // LEFT: main content - scrollable Expanded(child: _buildMainLeftContent()), // RIGHT: IR panel - desktop inline (only visible on wide screens) AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, width: showIRDocs ? panelWidth : 0, child: showIRDocs ? _buildIrDocsPanel(panelWidth, isMobile: false) : const SizedBox.shrink(), ), ], ), ), + Expanded( + child: isMobile + ? AnimatedSwitcher( + duration: Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + final offsetAnimation = Tween( + begin: Offset(0, 1), end: Offset(0, 0)) + .animate(animation); + return SlideTransition( + position: offsetAnimation, child: child); + }, + child: showIRDocs + ? buildIRDocsContent( + isDesktop: false, + key: ValueKey('mobile_ir'), + ) + : _buildMainLeftContent(key: ValueKey('main_left'),showIRDocs: showIRDocs), + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // LEFT: main content - scrollable + Expanded(child: _buildMainLeftContent(showIRDocs: showIRDocs)), + + // RIGHT: IR panel - desktop inline (only visible on wide screens) + AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + width: showIRDocs ? panelWidth : 0, + child: showIRDocs + ? buildIRDocsContent( + isDesktop: true, + panelWidth: panelWidth, + key: ValueKey('desktop_ir'), + ) + : SizedBox.shrink(), + ), + ], + ), + ), + ], + ), + ), + ); + } + + // ------------------------- + // Main left content extracted to keep code tidy + // ------------------------- + Widget _buildMainLeftContent({Key? key,required bool showIRDocs,}) { + return SingleChildScrollView( + key: key, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Claim Details card (unchanged) + Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: const Color(0xFFEBEBEB), + blurRadius: 14, + spreadRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// ----------------------------------------- + /// NAME + POLICY NAME SECTION + /// ----------------------------------------- + Responsive.isDesktop(context) + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _buildKeyValue( + 'Name', + '${widget.empName ?? ''} (${widget.empCode ?? ''})', + ), + ), + const SizedBox(width: 24), + Expanded( + child: _buildKeyValue( + 'Policy Name', + widget.clientPolicyNo == null || + widget.clientPolicyNo!.trim().isEmpty + ? (widget.policyType ?? '') + : '${widget.policyType ?? ''} - ${widget.clientPolicyNo}', + ), + ), + + // Expanded( + // child: _buildKeyValue( + // 'Policy Name', + // '${widget.policyType ?? ''} - ${widget.clientPolicyNo ?? ''}', + // ), + // ), + ], + ) + : SizedBox( + width: double.infinity, // ⬅️ forces full width + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildKeyValue( + 'Name', + '${widget.empName ?? ''} (${widget.empCode ?? ''})', + ), + const SizedBox(height: 12), + _buildKeyValue( + 'Policy Name', + '${widget.policyType ?? ''} - ${widget.clientPolicyNo ?? ''}', + ), + ], + ), + ), + + const SizedBox(height: 24), + + /// ----------------------------------------- + /// CLAIM AMOUNT + CLAIM NUMBER SECTION + /// ----------------------------------------- + Responsive.isDesktop(context) + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _buildKeyValue( + 'Claims Amount', + (widget.claimAmount == null || + widget.claimAmount!.trim().isEmpty) + ? 'N/A' + : '₹${widget.claimAmount}', + ), + ), + const SizedBox(width: 24), + Expanded( + child: _buildKeyValue( + 'Claims Number', + (widget.claimNo == null || + widget.claimNo!.trim().isEmpty) + ? 'N/A' + : widget.claimNo!, + ), + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildKeyValue( + 'Claims Amount', + (widget.claimAmount == null || + widget.claimAmount!.trim().isEmpty) + ? 'N/A' + : '₹${widget.claimAmount}', + ), + const SizedBox(height: 8), + _buildKeyValue( + 'Claims Number', + (widget.claimNo == null || + widget.claimNo!.trim().isEmpty) + ? 'N/A' + : widget.claimNo!, + ), + ], + ), + ], + ), + ), + + SizedBox(height: 10), + + // Loading or step content + isLoading + ? Container( + child: Center( + child: Image.asset( + height: 60, width: 60, 'assets/nhance-loader.gif'))) + : Container( + child: getClaimsHistoryList.isNotEmpty + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(stepKeys.length, (index) { + String stepTitleKey = stepKeys[index]; + Map stepData = + stepMap[stepTitleKey]; + Widget content = _getStepContentFromApi(stepData); + return _buildStep( + stepNumber: index + 1, + title: + _getStepTitleFromApi(stepTitleKey, stepData), + content: content, + isLast: index == stepKeys.length - 1, + ); + }), + ) + : Container( + height: MediaQuery.of(context).size.height * 0.4, + child: Center( + child: Column( + children: [ + Image.asset('assets/searchData.jpg', + width: 200, height: 200, fit: BoxFit.cover), + Text('No Claims History', + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 15)), + ], + ), + ), + ), + ), + + // Claim files list + // Container( + // margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), + // child: claimFiles.isEmpty + // ? const Center(child: Text("No files available")) + // : SizedBox( + // height: 300, + // child: ListView.builder( + // itemCount: claimFiles.length, + // itemBuilder: (context, index) { + // final file = claimFiles[index]; + // return Card( + // child: ListTile( + // leading: const Icon(Icons.insert_drive_file, + // color: Colors.blue), + // title: Text(file['claim_file_name']), + // trailing: + // const Icon(Icons.download, color: Colors.green), + // onTap: () => _launchURL(file['url']), + // ), + // ); + // }, + // ), + // ), + // ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), + // padding: const EdgeInsets.all(16), + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(6), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.08), + // blurRadius: 6, + // offset: const Offset(0, 2), + // ), + // ], + // ), + child: claimFiles.isEmpty + ? const Center(child: Text("No files available")) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Submitted Documents', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 12), + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: claimFiles.length, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: showIRDocs ? 2 : 3, // ✅ desktop: 3 per row + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 5.9, // controls height + ), + itemBuilder: (context, index) { + final file = claimFiles[index]; + + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => _launchURL(file['url']), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + /// FILE ICON + Container( + child: const Icon( + Icons.insert_drive_file, + color: Color(0xFF3B5BDB), + size: 25, + ), + ), + + const SizedBox(width: 10), + + /// FILE NAME + Expanded( + child: Text( + file['claim_file_name'] ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ), + + const SizedBox(width: 8), + + /// DOWNLOAD ICON + InkWell( + borderRadius: BorderRadius.circular(6), + onTap: () => _launchURL(file['url']), + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: const Color(0xFFE6E6E6), + borderRadius: BorderRadius.circular(6), + ), + child: const Icon( + Icons.download, + size: 18, + color: Colors.black, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ], + ), + ) + ], + ), + ); + } + + Widget buildIRDocsContent({ + required bool isDesktop, + Key? key, + double? panelWidth, + }) { + return Container( + key: key ?? ValueKey("ir_docs_unified"), + width: isDesktop ? panelWidth ?? 320 : double.infinity, + height: double.infinity, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide( + color: Responsive.isDesktop(context) + ? Colors.grey.shade300 + : Colors.transparent, + width: Responsive.isDesktop(context) ? 1 : 0)), + ), + // color: Colors.white, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ----------------------------- + // HEADER (same for mobile + web) + // ----------------------------- + Row( + children: [ + Expanded( + child: Text( + "Additional Documents", + style: GoogleFonts.poppins( + fontSize: isDesktop ? 16 : 18, + fontWeight: FontWeight.w600, + ), + ), + ), + IconButton( + icon: Icon(Icons.close), + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + ), + ], + ), + + // SizedBox(height: isDesktop ? 8 : 8), + // + // Text( + // "Select documents, click Upload to assign the same file to all selected items.", + // style: GoogleFonts.poppins( + // fontSize: isDesktop ? 12 : 13, + // color: Colors.grey.shade700, + // ), + // ), + + SizedBox(height: 12), + + // ----------------------------- + // SCROLLABLE BODY + // ----------------------------- + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + // Checkbox list (same UI for both) + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // ✅ two per row + crossAxisSpacing: 24, + mainAxisSpacing: 8, + childAspectRatio: + 5, // ✅ controls height (adjust if needed) + ), + itemCount: requiredDocsList.length, + itemBuilder: (context, index) { + final d = requiredDocsList[index]; + final title = d["document_name"] as String; + + return Row( + children: [ + Checkbox( + value: d["document_received"] as bool, + onChanged: (v) { + setState(() { + d["document_received"] = v; + }); + }, + ), + Expanded( + child: Text( + title, + style: const TextStyle(fontSize: 14), + ), + ), + ], + ); + }, + ), + // ...requiredDocsList.map((d) { + // final title = d["document_name"] as String; + // return CheckboxListTile( + // value: d["document_received"] as bool, + // onChanged: (v) => + // setState(() => d["document_received"] = v), + // title: Text(title), + // controlAffinity: isDesktop + // ? ListTileControlAffinity.leading + // : ListTileControlAffinity.trailing, + // contentPadding: EdgeInsets.symmetric(horizontal: 8), + // ); + // }).toList(), + + SizedBox(height: 12), + + // Multi uploader (same UI) + Padding( + padding: const EdgeInsets.all(16.0), + child: const MultiFileUploadWidget(forceMobile: true), + ), + + SizedBox(height: 12), + + // Assigned file preview + ..._assignedFiles.entries + .where((e) => e.value != null) + .map((e) { + final title = e.key; + final file = e.value!; + return Card( + margin: + EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: ListTile( + title: Text(title), + subtitle: Text( + file.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailing: IconButton( + icon: Icon(Icons.cancel, color: Colors.red), + onPressed: () => removeAssignedFile(title), + ), + ), + ); + }).toList(), + + SizedBox(height: 16), + ], + ), + ), + ), + + SizedBox(height: 8), + + // ----------------------------- + // SUBMIT BUTTON + // ----------------------------- + ElevatedButton( + onPressed: () => submitIRDocs(), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE26728), + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + child: isSubmitting + ? SizedBox( + height: 22, + width: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : Text( + 'Submit IR Docs', + style: GoogleFonts.poppins( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(height: 10), + + // ----------------------------- + // CANCEL BUTTON + // ----------------------------- + ElevatedButton( + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + minimumSize: Size(double.infinity, 50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: const BorderSide(color: Color(0xFFE26728)), + ), + ), + child: Text( + "Cancel", + style: GoogleFonts.poppins( + color: Color(0xFFE26728), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } + + // ------------------------- + // Mobile full IR Docs panel (replaces the main content) + // ------------------------- + Widget _buildMobileFullIrDocs(double panelWidth, {Key? key}) { + return Container( + key: key ?? ValueKey('mobile_full_ir'), + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(12), + color: Colors.white, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // header row with close (keeps popup header outside) + Row( + children: [ + Expanded( + child: Text("IR Docs", + style: GoogleFonts.poppins( + fontSize: 18, fontWeight: FontWeight.w600))), + IconButton( + icon: Icon(Icons.close), + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + ), + ], + ), + SizedBox(height: 8), + Text( + "Select documents, click Upload to assign the same file to all selected items.", + style: GoogleFonts.poppins( + fontSize: 13, color: Colors.grey.shade700)), + SizedBox(height: 12), + + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + ...requiredDocsList.map((d) { + final title = d['document_name'] as String; + return CheckboxListTile( + value: d['document_received'] as bool, + onChanged: (v) => + setState(() => d['document_received'] = v), + title: Text(title), + controlAffinity: ListTileControlAffinity.trailing, + contentPadding: EdgeInsets.symmetric(horizontal: 8.0), + ); + }).toList(), + + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const MultiFileUploadWidget(forceMobile: true), + ], + ), + ), + // Padding( + // padding: const EdgeInsets.symmetric(horizontal: 8.0), + // child: ElevatedButton.icon( + // onPressed: handleUploadForSelected, + // icon: Icon(Icons.upload_file), + // label: Text("Upload for selected"), + // style: ElevatedButton.styleFrom( + // backgroundColor: Color(0xFF00A5A8), + // minimumSize: Size(double.infinity, 50), + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + // ), + // ), + // ), + + SizedBox(height: 12), + + ..._assignedFiles.entries + .where((e) => e.value != null) + .map((e) { + final title = e.key; + final file = e.value!; + return Card( + margin: + EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: ListTile( + title: Text(title), + subtitle: Text(file.name, + maxLines: 1, overflow: TextOverflow.ellipsis), + trailing: IconButton( + icon: Icon(Icons.cancel, color: Colors.red), + onPressed: () => removeAssignedFile(title)), + ), + ); + }).toList(), + + SizedBox(height: 16), + ], + ), + ), + ), + + Container( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: () { + submitIRDocs(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE26728), // Orange button + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Submit IR Docs', + style: GoogleFonts.poppins( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + SizedBox(height: 10), + Container( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: const BorderSide(color: Color(0xFFE26728)), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Cancel', + style: + GoogleFonts.poppins(color: const Color(0xFFE26728)), + ), + ], + ), + ), + ) + ], + ), + ), + ); + } + + // ------------------------- + // Builds IR Docs panel (desktop) + // ------------------------- + Widget _buildIrDocsPanel(double panelWidth, {required bool isMobile}) { + // The panel has internal scrolling so it won't overflow + return Container( + width: panelWidth, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + border: Border(left: BorderSide(color: Colors.grey.shade300, width: 1)), + ), + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // header row + Row( + children: [ + Expanded( + child: Text("IR Docs", + style: GoogleFonts.poppins( + fontSize: 16, fontWeight: FontWeight.w600))), + IconButton( + icon: Icon(Icons.close), + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + ) + ], + ), + SizedBox(height: 8), + Text( + "Select documents, click Upload to assign the same file to all selected items.", + style: GoogleFonts.poppins( + fontSize: 12, color: Colors.grey.shade700)), + SizedBox(height: 12), + + // list + upload + preview inside scroll + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + // checkboxes + ...requiredDocsList.map((d) { + final title = d['document_name'] as String; + return CheckboxListTile( + value: d['document_received'] as bool, + onChanged: (v) => + setState(() => d['document_received'] = v), + title: Text(title), + ); + }).toList(), + + SizedBox(height: 8), + + // upload button + // Padding( + // padding: const EdgeInsets.symmetric(horizontal: 4.0), + // child: ElevatedButton.icon( + // onPressed: handleUploadForSelected, + // icon: Icon(Icons.upload_file), + // label: Text("Upload for selected"), + // style: ElevatedButton.styleFrom( + // backgroundColor: Color(0xFF00A5A8), + // minimumSize: Size(double.infinity, 44), + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)), + // ), + // ), + // ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const MultiFileUploadWidget(forceMobile: true), + ], + ), + ), + + SizedBox(height: 12), + + // assigned files preview + ..._assignedFiles.entries + .where((e) => e.value != null) + .map((e) { + final title = e.key; + final file = e.value!; + return Card( + child: ListTile( + title: Text(title), + subtitle: Text(file.name, + maxLines: 1, overflow: TextOverflow.ellipsis), + trailing: IconButton( + icon: Icon(Icons.cancel, color: Colors.red), + onPressed: () => removeAssignedFile(title)), + ), + ); + }).toList(), + + SizedBox(height: 16), + ], + ), + ), + ), + + // Submit button (keeps at bottom) + + Container( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: () { + submitIRDocs(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE26728), // Orange button + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Submit IR Docs', + style: GoogleFonts.poppins( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + SizedBox(height: 10), + Container( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: () { + _resetIRDocs(); + setState(() => showIRDocs = false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: const BorderSide(color: Color(0xFFE26728)), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Cancel', + style: + GoogleFonts.poppins(color: const Color(0xFFE26728)), + ), + ], + ), + ), + ) + ], + ), + ), + ); + } + + // ------------------------- + // UI Helpers & existing functions below (unchanged) + // ------------------------- + Widget _buildStep({ + required int stepNumber, + required Widget title, + required Widget content, + bool isLast = false, + }) { + final noContent = (content as Column).children.isEmpty ? 0 : 1; + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + SizedBox(height: stepNumber == 1 ? 0 : 4), + Container( + height: 28, + width: 28, + decoration: BoxDecoration( + color: Color(0xFF00A5A8), shape: BoxShape.circle), + alignment: Alignment.center, + child: Text( + '$stepNumber', + style: TextStyle( + color: Colors.white, + fontSize: Responsive.isDesktop(context) ? 13 : 12, + fontWeight: FontWeight.w600, + ), + ), + ), + if (!isLast) + Container( + height: noContent == 1 ? 50 : 20, + width: 2, + margin: EdgeInsets.only(top: 4, bottom: 4), + child: CustomPaint(painter: DottedLinePainter()), + ), + ], + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title, + if (noContent == 1) SizedBox(height: noContent == 0 ? 0 : 8), + if (noContent == 1) + Container( + width: double.infinity, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Color(0xFFE0E0E0)), + ), + child: content, + ), + if (noContent == 1) SizedBox(height: isLast ? 0 : 16), + ], + ), + ), + ], + ); + } + + Widget _getStepTitleFromApi(String status, Map data) { + final modifiedBy = data['modified_by'] ?? ''; + final modifiedAt = data['modified_at'] ?? ''; + final symbol = + (data['modified_by'] != null && data['modified_by'] != '') ? ' - ' : ''; + final isDesktop = Responsive.isDesktop(context); + + return RichText( + text: TextSpan( + children: [ + TextSpan( + text: status + (isDesktop ? ' ' : '\n'), + style: GoogleFonts.poppins( + fontSize: Responsive.isDesktop(context) ? 15 : 11, + fontWeight: FontWeight.w500, + color: Color(0xFF212120)), + ), + TextSpan( + text: ' ($modifiedBy$symbol$modifiedAt)', + style: GoogleFonts.poppins( + fontSize: Responsive.isDesktop(context) ? 14 : 11, + fontWeight: FontWeight.w400, + color: Color(0xFF565656)), + ), + ], + ), + ); + } + + Widget _getStepContentFromApi(Map data) { + List rows = []; + data.forEach((key, value) { + if (key == 'modified_by' || key == 'modified_at') return; + String displayName = value['display_name'] ?? key; + String displayValue = value['display_value'] ?? 'N/A'; + rows.add(_buildHistoryListData(displayName, displayValue)); + rows.add(SizedBox(height: 1)); + }); + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: rows); + } + + // Widget _buildKeyValue(String title, String value) { + // final displayValue = + // (value == null || value.trim().isEmpty) ? 'N/A' : value; + // return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Text(title, + // style: GoogleFonts.poppins( + // color: Color(0xFF747474), + // fontWeight: FontWeight.w400, + // fontSize: Responsive.isDesktop(context) ? 16 : 13)), + // SizedBox(height: 4), + // Text(displayValue, + // style: GoogleFonts.poppins( + // color: Color(0xFF000000), + // fontWeight: FontWeight.w500, + // fontSize: Responsive.isDesktop(context) ? 16 : 13)), + // ]); + // } + + Widget _buildKeyValue(String title, String? value) { + final displayValue = + (value == null || value.trim().isEmpty) ? 'N/A' : value.trim(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: GoogleFonts.poppins( + color: const Color(0xFF747474), + fontWeight: FontWeight.w400, + fontSize: Responsive.isDesktop(context) ? 16 : 13, + ), + ), + const SizedBox(height: 4), + + /// Prevent overflow everywhere (web & mobile) + Text( + displayValue, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: GoogleFonts.poppins( + color: const Color(0xFF000000), + fontWeight: FontWeight.w500, + fontSize: Responsive.isDesktop(context) ? 16 : 13, + ), + ), + ], + ); + } + + Widget _buildHistoryListData(String title, String value) { + final displayValue = + (value == null || value.trim().isEmpty) ? 'N/A' : value; + final titleText = Text(title, + style: GoogleFonts.poppins( + color: const Color(0xFF747474), + fontWeight: FontWeight.w400, + fontSize: Responsive.isDesktop(context) ? 14 : 12)); + final valueText = Text(displayValue, + style: GoogleFonts.poppins( + color: const Color(0xFF000000), + fontWeight: FontWeight.w400, + fontSize: Responsive.isDesktop(context) ? 14 : 12), + textAlign: TextAlign.right); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: Responsive.isDesktop(context) + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Align( + alignment: Alignment.centerLeft, child: titleText)), + Expanded( + child: Align( + alignment: Alignment.centerRight, child: valueText)), + ]) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [titleText, SizedBox(height: 4), valueText]), + ); + } +} + +class DottedLinePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + const dashHeight = 2.0; + const dashSpace = 3.0; + double startY = 0; + final paint = Paint() + ..color = Colors.grey.shade400 + ..strokeWidth = 1; + while (startY < size.height) { + canvas.drawLine(Offset(0, startY), Offset(0, startY + dashHeight), paint); + startY += dashHeight + dashSpace; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/excelVerification.dart b/lib/presentation/excelVerification.dart new file mode 100644 index 0000000..58e962a --- /dev/null +++ b/lib/presentation/excelVerification.dart @@ -0,0 +1,767 @@ +import 'dart:ui'; + +import 'package:csv/csv.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import 'package:nhancepolicy/presentation/preFileUpload.dart'; +import 'package:nhancepolicy/presentation/postFileUpload.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'dart:convert'; +import 'dart:async'; +import 'package:nhancepolicy/customAppBar/toastHelper.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; +import 'package:universal_html/html.dart' as html; +import 'dart:typed_data'; +import 'package:collection/collection.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../customAppBar/base_layout.dart'; + +class excelErrorScreen extends StatefulWidget { + final String ClientId; + final String policy_no; + final String action; + final String created_at; + final String clientBranchId; + final String Token; + final String TokenType; + final String id; + const excelErrorScreen({ + Key? key, + required this.ClientId, + required this.policy_no, + required this.action, + required this.created_at, + required this.clientBranchId, + required this.Token, + required this.TokenType, + required this.id, + }) : super(key: key); + + @override + State createState() => _activePolicyExcelErrorState(); +} + +class _activePolicyExcelErrorState extends State + with TickerProviderStateMixin { + final tokenService = TokenStorageService(); + bool isLoading = false; + dynamic empPrimaryId; + dynamic empClientId; + dynamic empClientBranchId; + dynamic empHrId; + dynamic enrollmentClient_id; + dynamic enrollmentEmpClientBranchId; + dynamic enrollmentHrId; + TextEditingController searchController = TextEditingController(); + List excelHeader = []; + List>> excelData = []; + + late int excelValidationStaus = 1; + bool isSuccess = false; + String successContent = ''; + + List>> filteredExcelData = []; + + String? _postPreToken = ''; + + late ApiService apiService; + + int _currentPage = 1; + int _rowsPerPage = 5; + + List>> get _paginatedExcelData { + final start = (_currentPage - 1) * _rowsPerPage; + final end = + (_currentPage * _rowsPerPage).clamp(0, filteredExcelData.length); + return filteredExcelData.sublist(start, end); + } + + final ScrollController _verticalController = ScrollController(); + final ScrollController _horizontalController = ScrollController(); + + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + getCDPoliciesDetails(); + } + + @override + void dispose() { + _verticalController.dispose(); + _horizontalController.dispose(); + super.dispose(); + } + + + Future getCDPoliciesDetails() async { + setState(() { + isLoading = true; + }); + + try { + final response = + await apiService.getExcelFileErrorsApi(widget.id, widget.TokenType); + + // 🔴 CASE 1: Empty data → popup + back + if (response['data'] is List && response['data'].isEmpty) { + setState(() => isLoading = false); + _showEmptyDataDialog(response['message']); + return; + } + + // 🟢 CASE 2: Success with data + if (response['status'] == true) { + ToastHelper.showSuccessToast(context, response['message']); + + setState(() { + isLoading = false; + isSuccess = false; + excelValidationStaus = 1; + + excelHeader = + List.from(response['data']['excel_header']); + + excelData = (response['data']['excel_data'] as List) + .map>>( + (row) => row + .map>( + (cell) => Map.from(cell)) + .toList(), + ) + .toList(); + + filteredExcelData = List.from(excelData); + }); + } + // 🟡 CASE 3: API failed with message + else { + setState(() => isLoading = false); + _showEmptyDataDialog(response['message']); + } + + } catch (e) { + setState(() => isLoading = false); + print('Exception occurred: $e'); + _showEmptyDataDialog('Something went wrong. Please try again.'); + } + } + + + void _showEmptyDataDialog(String message) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: const Text( + 'Message', + style: TextStyle(fontWeight: FontWeight.w600), + ), + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); // close dialog + Navigator.of(context).pop(); // go back page + }, + child: const Text('Close'), + ), + ], + ); + }, + ); + } + + + void search(String query) { + if (query.isEmpty) { + setState(() { + filteredExcelData = List.from(excelData); + _currentPage = 1; + }); + return; + } + + final lowerQuery = query.toLowerCase(); + + setState(() { + filteredExcelData = excelData.where((row) { + return row.any((cell) { + final value = cell['value']; + return value != null && + value.toString().toLowerCase().contains(lowerQuery); + }); + }).toList(); + + _currentPage = 1; + }); + } + + // emp_is_active + + void exportToCsv({ + required List excelHeader, + required List>> excelData, + }) { + List> rows = []; + + /// 1️⃣ Add headers + rows.add(excelHeader); + + /// 2️⃣ Add rows + for (final row in excelData) { + rows.add( + row.map((cell) { + final value = cell['value']; + return value == null ? '' : value.toString(); + }).toList(), + ); + } + + /// 3️⃣ Convert to CSV + final csvData = const ListToCsvConverter().convert(rows); + + /// 4️⃣ Download (Flutter Web) + final bytes = utf8.encode(csvData); + final blob = html.Blob([bytes], 'text/csv'); + final url = html.Url.createObjectUrlFromBlob(blob); + + html.AnchorElement(href: url) + ..setAttribute("download", "Excel_Error_File.csv") + ..click(); + + html.Url.revokeObjectUrl(url); + } + + + // Future handleExportAction() async { + // print('handleExportAction'); + // + // final postId = await tokenService.readValue('empHrId'); + // final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + // + // var activity = "export_empdata"; + // + // var activityPre = "export_preempdata"; + // dynamic response; + // + // print('postId - $postId'); + // print('preId - $preId'); + // print('activity - $activity'); + // + // try { + // print('10'); + // + // if (widget.TokenType == 'pre') { + // response = await apiService.getPreLogHrActivity( + // postId!, preId!, widget.Token, activityPre); + // } else if (widget.TokenType == 'post') { + // response = await apiService.getPostLogHrActivity( + // postId!, preId!, widget.Token, activity); + // } + // + // if (response['status'] == 'success') { + // print('Request success'); + // } else { + // // ToastHelper.showWarningToast( + // // context, 'Request failed with status: ${response.statusCode}'); + // print('Request failed with status: ${response['code']}'); + // } + // } catch (e) { + // print('Exception occurred: $e'); + // } + // } + + String _capitalize(String? value) { + if (value == null || value.isEmpty) return ''; + return value[0].toUpperCase() + value.substring(1).toLowerCase(); + } + + String formatDateTime(String dateTime) { + final parsedDate = DateTime.parse(dateTime); + return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); + } + + + @override + Widget build(BuildContext context) { + return BaseLayout( + child: _buildContent(context), + ); + } + + 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( + 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( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.policy_no ?? '', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + if (widget.TokenType != 'pre') + RichText( + text: TextSpan( + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + ), + children: [ + TextSpan( + text: widget.action, + style: const TextStyle( + color: Color(0xFF00999E), + ), + ), + const TextSpan( + text: ' - ', + style: TextStyle( + color: Color(0xFF585858), + ), + ), + TextSpan( + text: formatDateTime(widget.created_at), + style: const TextStyle( + color: Color(0xFF585858), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + + /// 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), + ), + ), + ), + + const SizedBox(width: 12), + + /// ⬇️ Export Button + SizedBox( + width: 116, + height: 37, + child: ElevatedButton( + onPressed: () { + exportToCsv( + excelHeader: excelHeader, + excelData: filteredExcelData, // or excelData + ); + + }, + 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), + Expanded( + child: _buildCDDataTable(context), + ), + + ], + ), + ); + } + + Widget _buildCDDataTable(BuildContext context) { + return ScrollConfiguration( + behavior: const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.touch, + PointerDeviceKind.trackpad, + }, + ), + child: _buildScrollableTable(context), + ); + } + + Widget _buildScrollableTable(BuildContext context) { + const double columnWidth = 160; + final double tableWidth = excelHeader.length * columnWidth; + + return Scrollbar( + thumbVisibility: true, + controller: _verticalController, + child: SingleChildScrollView( + controller: _verticalController, + physics: const ClampingScrollPhysics(), // 👈 mouse wheel + scrollDirection: Axis.vertical, + child: Scrollbar( + thumbVisibility: true, + controller: _horizontalController, + notificationPredicate: (n) => n.depth == 1, + child: SingleChildScrollView( + controller: _horizontalController, + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: const Color(0xFFD7E9EB), + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: excelHeader.map((header) { + return SizedBox( + width: columnWidth, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12), + child: Text( + header, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + }).toList(), + ), + ), + + const SizedBox(height: 6), + + /// ROWS + ..._paginatedExcelData.map((row) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Color(0xFFA9D9DE), + width: 1, + ), + ), + ), + child: Row( + children: row.map((cell) { + final bool hasError = cell.containsKey('error'); + + return SizedBox( + width: columnWidth, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: hasError + ? Row( + children: [ + Expanded( + child: Text( + cell['value']?.toString() ?? '-', + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + const SizedBox(width: 6), + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: const Icon( + Icons.error_outline, + color: Colors.red, + size: 16, + ), + onPressed: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (_) { + final List errors = cell['error'] as List; + + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + width: 420, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// ERROR TITLE + Text( + 'Error!', + style: GoogleFonts.poppins( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + + const SizedBox(height: 16), + + /// RED ICON + Container( + width: 64, + height: 64, + decoration: const BoxDecoration( + color: Color(0xFFE0002A), + shape: BoxShape.circle, + ), + child: const Center( + child: Text( + '!', + style: TextStyle( + color: Colors.white, + fontSize: 36, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + + const SizedBox(height: 20), + + /// ERROR HEADING (optional – first error) + Text( + errors.isNotEmpty ? errors.first.toString() : 'Validation Error', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + + const SizedBox(height: 12), + + /// ERROR DETAILS + ...errors.skip(1).map( + (e) => Padding( + padding: const EdgeInsets.only(top: 6), + child: Text( + e.toString(), + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14, + color: const Color(0xFFE09B2D), // orange text + ), + ), + ), + ), + + const SizedBox(height: 20), + + /// OK BUTTON + SizedBox( + width: 120, + height: 30, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFE0002A), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + elevation: 0, + ), + child: Text( + 'OK', + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + + }, + ), + ], + ) + : Text( + cell['value']?.toString() ?? '-', + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + ); + }).toList(), + ), + ); + }).toList(), + + /// PAGINATION + SizedBox( + width: tableWidth, + child: _buildPagination(context), + ), + ], + ), + ), + ), + ), + ), + ); + } + + + Widget _buildPagination(BuildContext context) { + final totalPages = + (filteredExcelData.length / _rowsPerPage).ceil(); + + if (totalPages <= 1) { + return const SizedBox.shrink(); // 👈 hide if only one page + } + + return Row( + mainAxisAlignment: MainAxisAlignment.end, // 👈 right aligned + children: [ + DropdownButton( + value: _rowsPerPage, + items: [5, 10, 15, 20, 50].map((int value) { + return DropdownMenuItem( + value: value, + child: Text( + ' $value ', + style: GoogleFonts.poppins(fontSize: 14), + ), + ); + }).toList(), + onChanged: (newValue) { + setState(() { + _rowsPerPage = newValue!; + _currentPage = 1; + }); + }, + ), + + IconButton( + icon: const Icon(Icons.chevron_left), + onPressed: _currentPage > 1 + ? () => setState(() => _currentPage--) + : null, + ), + + for (int i = 1; i <= totalPages; i++) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: _currentPage == i + ? const Color(0xFF00A6A6) + : Colors.grey[300], + foregroundColor: + _currentPage == i ? Colors.white : Colors.black, + minimumSize: const Size(36, 36), + padding: EdgeInsets.zero, + ), + onPressed: () { + setState(() { + _currentPage = i; + }); + }, + child: Text(i.toString()), + ), + ), + + IconButton( + icon: const Icon(Icons.chevron_right), + onPressed: _currentPage < totalPages + ? () => setState(() => _currentPage++) + : null, + ), + ], + ); + } + + +} diff --git a/lib/presentation/hrDashboard.dart b/lib/presentation/hrDashboard.dart new file mode 100755 index 0000000..7055b64 --- /dev/null +++ b/lib/presentation/hrDashboard.dart @@ -0,0 +1,490 @@ +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 '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); + + @override + State createState() => _hrDashboardState(); +} + +class _hrDashboardState extends State + with SingleTickerProviderStateMixin { + late ApiService apiService; + bool isLoading = false; + String? selectedPolicyId; + List> activePoliciesList = []; + + String? _metabaseToken; + String? _metabaseUrl; + bool isPolicyLoading = false; + bool isDashboardLoading = false; + bool hasDashboardError = false; + + bool _metabaseLoaded = false; // ✅ FIX + List postModules = []; + dynamic policy_name; + dynamic getPreCardArrays = []; + dynamic getPostCardArrays = []; + String? _postPreToken = ''; + dynamic empClientBranchId; + dynamic empHrId; + dynamic empClientId; + int stausVal = 1; + String _dashboardViewType = ''; + bool isDropdownOpen = false; + final FocusNode _policyFocusNode = FocusNode(); + + + + final tokenService = TokenStorageService(); + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + + _policyFocusNode.addListener(() { + if (!_policyFocusNode.hasFocus) { + setState(() => isDropdownOpen = false); + } + }); + + _loadToken(); + } + + @override + void dispose() { + _policyFocusNode.dispose(); + super.dispose(); + } + + Future _loadToken() async { + _postPreToken = await tokenService.getCurrentToken(); + final postRaw = await tokenService.readValue('empAllowed_modules'); + + postModules = postRaw != null && postRaw.isNotEmpty + ? List.from(jsonDecode(postRaw)) + : []; + + if (!(postModules.contains(2) || + postModules.contains(3) || + postModules.contains(4))) { + // ❌ No dashboard permission + setState(() { + hasDashboardError = true; + }); + return; + } + + empClientId = await tokenService.readValue('empClientId'); + empClientBranchId = await tokenService.readValue('empClientBranchId'); + empHrId = await tokenService.readValue('empHrId'); + + 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; + // }); + try { + if (empClientBranchId == null || empClientId == null) { + return; + } + final response = await apiService.getActiveCashDepositDetailsToApi( + empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal); + + // final response = await apiService.getCashDepositDetailsToApi( + // clintID!, clintBranchId!, hr_id, token); + print('IN1'); + if (response['status'] == 'success') { + final list = List>.from(response['data']); + + 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; + }); + } + } 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()); + } + } + + Future _loadDashboardByPolicy(String clientPolicyId) async { + try { + setState(() { + isDashboardLoading = true; + _metabaseLoaded = false; + }); + + final response = await apiService.postHrDashboard({ + "client_id": empClientId, + "client_policy_id": clientPolicyId, + }, _postPreToken); + + if (response['status'] == 'success') { + _registerMetabaseIframe( + token: response['data']['metabaseToken'], + url: response['data']['metabaseUrl'], + clientPolicyId: clientPolicyId, + ); + + setState(() { + _metabaseLoaded = true; + }); + } else { + ToastHelper.showErrorToast(context, response['message']); + } + } catch (e) { + ToastHelper.showErrorToast(context, 'Dashboard loading failed'); + } finally { + setState(() { + isDashboardLoading = false; + }); + } + } + + + + void _registerMetabaseIframe({ + required String token, + required String url, + required String clientPolicyId, + }) { + _dashboardViewType = 'metabase-dashboard-$clientPolicyId'; + + final htmlContent = _buildMetabaseHtml( + token: token, + url: url, + ); + + 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( + _dashboardViewType, + (int viewId) => iframe, + ); + } + + + + + 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; + } + + @override + Widget build(BuildContext 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( + child: isDashboardLoading + ? Center( + child: Image.asset( + 'assets/nhance-loader.gif', + height: 60, + width: 60, + ), + ) + : _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), + ), + ), + ), + + + ), + ); + } + + Widget _buildPolicySelector() { + return Container( + padding: const EdgeInsets.all(16), + color: Colors.white, + child: Row( + children: [ + const Text( + 'Select Policy', + style: TextStyle(fontWeight: FontWeight.w600), + ), + 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), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDashboard() { + if (isDashboardLoading) { + return Center( + child: Image.asset( + 'assets/nhance-loader.gif', + height: 60, + width: 60, + ), + ); + } + + if (!_metabaseLoaded || _dashboardViewType.isEmpty) { + return const Center( + child: Text( + 'No dashboard data available', + style: TextStyle(color: Colors.grey), + ), + ); + } + + return HtmlElementView(viewType: _dashboardViewType); + } + + + + String _buildMetabaseHtml({ + required String token, + required String url, + }) { + return ''' + + + + + Metabase Dashboard + + + + + + + + + + + + +'''; + } + + +} \ No newline at end of file diff --git a/lib/hrPolicyDetails.dart b/lib/presentation/hrPolicyDetails.dart similarity index 57% rename from lib/hrPolicyDetails.dart rename to lib/presentation/hrPolicyDetails.dart index 235ace5..3dcdaba 100755 --- a/lib/hrPolicyDetails.dart +++ b/lib/presentation/hrPolicyDetails.dart @@ -2,16 +2,16 @@ 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/excel_verification.dart'; -import 'package:nhancepolicy/postFileUpload.dart'; +import 'package:nhancepolicy/presentation/preFileUpload.dart'; +import 'package:nhancepolicy/presentation/postFileUpload.dart'; import 'package:nhancepolicy/service/api_service.dart'; -import 'package:nhancepolicy/service/hrDashboardTabs/claims.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:nhancepolicy/presentation/claims.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'dart:convert'; import 'dart:async'; import 'package:http/http.dart' as http; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:universal_html/html.dart' as html; import 'dart:typed_data'; import 'dart:io'; @@ -22,7 +22,8 @@ import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:collection/collection.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'customAppBar/customFooter.dart'; +import '../customAppBar/base_layout.dart'; +import '../customAppBar/customFooter.dart'; import 'hrDashboard.dart'; class hrPolicyDetails extends StatefulWidget { @@ -38,6 +39,8 @@ class hrPolicyDetails extends StatefulWidget { final String cardInsurer_name; final String cardPolicy_name; final String cardPolicy_ExpDate; + final String total_premium; + final int is_ecard_bulk_download_for_employee; const hrPolicyDetails( {Key? key, @@ -52,7 +55,9 @@ class hrPolicyDetails extends StatefulWidget { required this.cardPolicyNo, required this.cardInsurer_name, required this.cardPolicy_name, - required this.cardPolicy_ExpDate}) + required this.cardPolicy_ExpDate, + required this.total_premium, + required this.is_ecard_bulk_download_for_employee}) : super(key: key); @override @@ -61,6 +66,16 @@ class hrPolicyDetails extends StatefulWidget { class _HrPolicyDetailsState extends State with TickerProviderStateMixin { + final tokenService = TokenStorageService(); + dynamic empPrimaryId; + dynamic empClientId; + dynamic empClientBranchId; + dynamic empHrId; + dynamic enrollmentClient_id; + dynamic enrollmentEmpClientBranchId; + dynamic enrollmentHrId; + + String? _postPreToken = ''; Uint8List? fileBytes; String empCodeFromHrPolcy = ''; bool hasAnyEcardLink = false; @@ -76,6 +91,7 @@ class _HrPolicyDetailsState extends State List> originalData = []; // Original data source List> filteredData = []; // Filtered data source + dynamic argumentsData; dynamic policyType; dynamic policyName; @@ -92,6 +108,13 @@ class _HrPolicyDetailsState extends State int _currentPage = 1; int _rowsPerPage = 5; + /// holds selected employee ids as String or int (be consistent) + final Set selectedEmployeeIds = {}; + + /// ids shown in the current page (for header checkbox) + List currentPageIds = []; + + List get _paginatedData { final startIndex = (_currentPage - 1) * _rowsPerPage; final endIndex = @@ -99,16 +122,45 @@ class _HrPolicyDetailsState extends State return filteredData.sublist(startIndex, endIndex); } + Color getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'draft': + return const Color(0xFFF9EBBD); + case 'enrolled': + return const Color(0xFFBDF9D9); + case 'total': + return const Color(0xFFE2FBCB); + default: + return const Color(0xFFB0BEC5); + } + } + @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here - getCDPoliciesDetails(); - print("_PreEnrollmentState 1"); + print("_PreEnrollmentState 1"); + getCDPoliciesDetails(); print('allowed_modules'); } + // Future _loadToken() async { + // _postPreToken = tokenService.getCurrentToken(); + // if(widget.TokenType == "post") { + // empClientId = await tokenService.readValue('empClientId'); + // empClientBranchId = await tokenService.readValue('empClientBranchId'); + // empHrId = await tokenService.readValue('empHrId'); + // } + // if(widget.TokenType == "pre") { + // enrollmentClient_id = await tokenService.readValue('enrollmentClient_id'); + // enrollmentEmpClientBranchId = await tokenService.readValue('enrollmentEmpClientBranchId'); + // enrollmentHrId = await tokenService.readValue('enrollmentHrId'); + // } + // + // getCDPoliciesDetails(); + // } + Future getCDPoliciesDetails() async { print('9'); setState(() { @@ -117,8 +169,7 @@ class _HrPolicyDetailsState extends State try { print('10'); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - modulesString = prefs.getString('empAllowed_modules'); + modulesString = await tokenService.readValue('empAllowed_modules'); // modulesString = "[2,3]"; print("empmodulesString - $modulesString"); @@ -139,7 +190,7 @@ class _HrPolicyDetailsState extends State ? await apiService.getEmployeeAndDependenceToApi(widget.ClientId, widget.ClientPoliyId, widget.clientBranchId, widget.Token) : await apiService.getEmployeeAndDependenceToApiPre(widget.ClientId, - widget.ClientPoliyId, widget.clientBranchId, widget.Token); + widget.ClientPoliyId, widget.clientBranchId, widget.Token!); if (response['status'] == 'success') { setState(() { @@ -173,14 +224,16 @@ class _HrPolicyDetailsState extends State } } - Future getEcardDownload(String? empCode,String? empId,String? clientPolicyId,String? policyNo) async { + Future getEcardDownload(String? empCode, String? empId, + String? clientPolicyId, String? policyNo) async { final eCarDParams = { 'id': empId, 'emp_code': empCode, 'client_policy_id': clientPolicyId, 'policy_no': policyNo }; - final response = await apiService.getEcardRequest(eCarDParams,widget.Token); + final response = + await apiService.getEcardRequest(eCarDParams, widget.Token); print('check 1'); final ecardDownloadUrl = response['data']['eCardDownload']; final message = response['data']['message']; @@ -308,9 +361,9 @@ class _HrPolicyDetailsState extends State Future handleExportAction() async { print('handleExportAction'); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final postId = prefs.getString('empHrId'); - final preId = prefs.getString('enrollmentEmpPrimaryId'); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + var activity = "export_empdata"; var activityPre = "export_preempdata"; @@ -348,6 +401,75 @@ 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); + if (response['status'] == true) { + print('Request success'); + _showBulkDownloadSuccessPopup(response['message']); + } else { + ToastHelper.showErrorToast(context, response['message']); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + void _showBulkDownloadSuccessPopup(String message) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle, + color: Color(0xFF009195), + size: 60, + ), + const SizedBox(height: 16), + Text( + message, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF009195), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('OK',style: GoogleFonts.poppins( + color: Colors.white + ),), + ), + ), + ], + ), + ); + }, + ); + } + @override void dispose() { _tabController.dispose(); @@ -356,265 +478,274 @@ class _HrPolicyDetailsState extends State @override Widget build(BuildContext context) { - // TODO: implement build - return Scaffold( - appBar: CustomAppBar(), - backgroundColor: Color(0xFFEFF3F6), - body: Stack(children: [ - // color: Color(0xFFEFF3F6), + return BaseLayout( + child: _buildContent(context), + ); + } - // padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 40), - SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Container( - padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50), + 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( children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, + 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( - // color: Colors.greenAccent.shade100, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IconButton( - padding: - EdgeInsets.zero, // Removes default padding - constraints: BoxConstraints(), - onPressed: () { - Navigator.pop(context); - }, - icon: Icon( - Icons.arrow_back_ios, - color: Colors.grey, - size: 17, - )), - // IconButton( - // padding: EdgeInsets.zero, // Removes default padding - // constraints: BoxConstraints(), - // onPressed: () {}, - // icon: Icon( - // Icons.arrow_back_ios, - // color: Colors.grey, - // // size: 12, - // )), - ], + 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), + ), ), ), - 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, + + 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), ), - 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, - ), + ), + 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, ), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.circular(10), // 👈 set your desired radius + 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), + // padding: const EdgeInsets.all(16.0), child: Column( children: [ - Row( - children: [ - Expanded( - flex: 4, - child: Align( - alignment: Alignment.centerLeft, - child: Container( - width: 400, - height: 37, - decoration: BoxDecoration( - color: Color(0xFFF0F0F0), - borderRadius: BorderRadius.circular(8), - ), - child: TextField( - decoration: InputDecoration( - hintText: 'Search', - prefixIcon: Icon(Icons.search, size: 18), - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 8), - border: InputBorder - .none, // No border since Container handles it - ), - controller: searchController, - onChanged: search, - style: TextStyle(fontSize: 14), - ), - ), - ), - ), - SizedBox(width: 12), - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - // if (widget.TokenType != "post") - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => widget - .TokenType != - "post" - ? excelVerify( - 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, - - // 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, - - // Token: widget.Token, - // ClientId: widget.ClientId, - // ClientPolicyId : widget.ClientPoliyId, - // PolicyName: widget.cardPolicy_name, - // PolicyNo: widget.cardPolicyNo, - // ClientBranchId: widget.HrId, - // PolicyType: widget.cardType, - )), - ); - // exportToCsv(filteredData); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - padding: - EdgeInsets.all(10), // Internal padding - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 10), // Border radius - side: BorderSide( - color: Colors - .transparent, // Optional border color - width: 1, // Border width - ), - ), - elevation: 0, - ), - child: Text( - 'Import', - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFFFFFFFF), - fontWeight: FontWeight.w500, - letterSpacing: 1), - ), - ), - SizedBox( - width: 10, - ), - ElevatedButton( - onPressed: () { - exportToCsv(filteredData); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFFE26728), - padding: - EdgeInsets.all(10), // Internal padding - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 10), // Border radius - side: BorderSide( - color: Colors - .transparent, // Optional border color - width: 1, // Border width - ), - ), - elevation: 0, - ), - child: Text( - 'Export', - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFFFFFFFF), - fontWeight: FontWeight.w500, - letterSpacing: 1), - ), - ), - ], - ), - ), - ], - ), - SizedBox(height: 20), Row( children: [ Expanded( @@ -630,30 +761,81 @@ class _HrPolicyDetailsState extends State ], ), ), + 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, + ), + ), + ), ], ), - ), - ), + ); + } - if (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 + Map getFixedStatusCounts() { + int draftCount = 0; + int enrolledCount = 0; + + for (final item in filteredData) { + final status = item['status']?.toString().toLowerCase(); + + if (status == 'draft') { + draftCount++; + } else if (status == 'enrolled') { + enrolledCount++; + } + } + + return { + 'draft': draftCount, + 'enrolled': enrolledCount, + 'total': filteredData.length, + }; + } + + Widget _buildStatusSummary() { + if (filteredData.isEmpty) { + return const SizedBox(); + } + + final statusCounts = getFixedStatusCounts(); + final List order = ['draft', 'enrolled', 'total']; + + return SizedBox( + height: 34, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: order.length, + separatorBuilder: (_, __) => const SizedBox(width: 10), + itemBuilder: (context, index) { + final status = order[index]; + final count = statusCounts[status] ?? 0; + final color = getStatusColor(status); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(6), ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - width: double.infinity, // Make the footer full width - child: CustomFooter(), - ), - ), - ]), + child: Text( + '${status.toUpperCase()} - $count', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ); + }, + ), ); } @@ -687,6 +869,13 @@ class _HrPolicyDetailsState extends State ); } + final currentPageIds = _paginatedData + .map((e) => e['id']?.toString()) + .whereType() + .toList(); + + + return Container( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -700,6 +889,25 @@ class _HrPolicyDetailsState extends State padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: Row( children: [ + if (widget.TokenType == 'post' && + widget.is_ecard_bulk_download_for_employee == 1) + SizedBox( + width: 40, + child: Checkbox( + value: currentPageIds.isNotEmpty && + currentPageIds.every(selectedEmployeeIds.contains), + onChanged: (checked) { + setState(() { + if (checked == true) { + selectedEmployeeIds.addAll(currentPageIds); + } else { + selectedEmployeeIds.removeAll(currentPageIds); + } + }); + }, + ), + + ), Expanded( flex: 3, child: Text( @@ -799,12 +1007,6 @@ class _HrPolicyDetailsState extends State const SizedBox(height: 6), - // Table body rows - // ...filteredData.mapIndexed((index, item) { - - // Expanded( - // child: - SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( @@ -815,19 +1017,42 @@ class _HrPolicyDetailsState extends State padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16), decoration: BoxDecoration( - color: Colors.white, + // color: Colors.white, border: Border( bottom: BorderSide( // color: Color(0xFFA1A1A1), - color: Color(0xFFD7E9EB), + color: Color(0xFFA9D9DE), width: 1, ), ), // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white, - borderRadius: BorderRadius.circular(6), + // borderRadius: BorderRadius.circular(6), ), child: Row( children: [ + if (widget.TokenType == 'post' && + widget.is_ecard_bulk_download_for_employee == 1) + SizedBox( + width: 40, + child: Checkbox( + value: selectedEmployeeIds + .contains(item['id']?.toString()), + onChanged: (checked) { + setState(() { + final id = item['id']?.toString(); + if (id == null) return; + + if (checked == true) { + selectedEmployeeIds.add(id); + } else { + selectedEmployeeIds.remove(id); + } + }); + }, + ), + + ), + Expanded( flex: 3, child: Column( @@ -919,11 +1144,11 @@ class _HrPolicyDetailsState extends State padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 4), decoration: BoxDecoration( - color: Color(0xFF7BD9B6), + color: getStatusColor(item['status'] ?? ''), // color: (item['emp_is_active'] == "1") // ? Color(0xFF7BD9B6) // : Color(0xFFFFA6A6), - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(10), ), child: Align( alignment: Alignment.center, @@ -938,92 +1163,104 @@ class _HrPolicyDetailsState extends State ), ), ), - - if (widget.TokenType != "pre" && (hasAnyEcardLink || hasModule)) -Expanded( - flex: 3, - child: Builder( - builder: (context) { - final isSelf = item['relationship'] == 'Self'; - final hasEcard = item['ecard_download_link'] != null; - final showEcard = isSelf && hasEcard; - final showClaim = widget.TokenType == "post" && hasModule; - if (!showEcard && !showClaim) { - return SizedBox(); // No icon to show - } + if (widget.TokenType != "pre" && + (hasAnyEcardLink || hasModule)) + Expanded( + flex: 3, + child: Builder( + builder: (context) { + final isSelf = item['relationship'] == 'Self'; + final hasEcard = + item['ecard_download_link'] != null; + final showEcard = isSelf && hasEcard; + final showClaim = + widget.TokenType == "post" && hasModule; - return Row( - mainAxisAlignment: showEcard && !showClaim - ? MainAxisAlignment.start // Only eCard, push to right - : MainAxisAlignment.end, // eCard + claim OR only claim - children: [ - if (showEcard) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: GestureDetector( - onTap: () { - getEcardDownload(item['emp_code'],item['employee_id'],item['client_policy_id'],item['policy_no']); - }, - child: Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: Color(0xFFE6F5F6), - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: EdgeInsets.all(4), - child: Image.asset( - 'assets/credit_card.png', - fit: BoxFit.contain, - ), - ), - ), - ), - ), + if (!showEcard && !showClaim) { + return SizedBox(); // No icon to show + } - if (showEcard && showClaim) SizedBox(width: 8), + return Row( + mainAxisAlignment: showEcard && !showClaim + ? MainAxisAlignment + .start // Only eCard, push to right + : MainAxisAlignment + .end, // eCard + claim OR only claim + children: [ + if (showEcard) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0), + child: GestureDetector( + onTap: () { + getEcardDownload( + item['emp_code'], + item['employee_id'], + item['client_policy_id'], + item['policy_no']); + }, + child: Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: Color(0xFFE6F5F6), + borderRadius: + BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(4), + child: Image.asset( + 'assets/credit_card.png', + fit: BoxFit.contain, + ), + ), + ), + ), + ), + if (showEcard && showClaim) + SizedBox(width: 8), + if (showClaim) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0), + child: GestureDetector( + onTap: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => hrDashboard( + // selectedIndex: 3, + // empCodeFromHrPolicy: item['emp_code']!, + // isHrcode: 1, + // ), + // ), + // ); + }, + child: Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: Color(0xFFE6F5F6), + borderRadius: + BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(4), + child: Image.asset( + 'assets/claim.png', + fit: BoxFit.contain, + ), + ), + ), + ), + ), + ], + ); + }, + ), + ), - if (showClaim) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => hrDashboard( - selectedIndex: 3, - empCodeFromHrPolicy: item['emp_code']!, - isHrcode: 1, - ), - ), - ); - }, - child: Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: Color(0xFFE6F5F6), - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: EdgeInsets.all(4), - child: Image.asset( - 'assets/claim.png', - fit: BoxFit.contain, - ), - ), - ), - ), - ), - ], - ); - }, - ), -), - // (hasAnyEcardLink || hasModule)) // Expanded( // flex: 3, diff --git a/lib/presentation/policies.dart b/lib/presentation/policies.dart new file mode 100644 index 0000000..475779e --- /dev/null +++ b/lib/presentation/policies.dart @@ -0,0 +1,987 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:jwt_decode/jwt_decode.dart'; +import 'package:nhancepolicy/responsive.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/hrDashboardTabs/activePolicies.dart'; +import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart'; +import 'package:nhancepolicy/presentation/claims.dart'; +import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; +import 'package:http/http.dart' as http; +import 'package:universal_html/html.dart' as html; +import 'package:intl/intl.dart'; + +import '../config/environment.dart'; +import '../customAppBar/base_layout.dart'; +import '../customAppBar/toastHelper.dart'; +import 'hrPolicyDetails.dart'; + +class policies extends StatefulWidget { + const policies({Key? key}) : super(key: key); + + @override + State createState() => _policiesState(); +} + + +class _policiesState extends State + with SingleTickerProviderStateMixin { + late ApiService apiService; + bool isLoading = false; + int isHrcode = 0; + + String? _postPreToken = ''; + + dynamic enrollmentClient_id; + dynamic policy_name; + dynamic getPreCardArrays = []; + dynamic getPostCardArrays = []; + + dynamic empClientBranchId; + dynamic empHrId; + int stausVal = 1; + int selectedIndex = 1; + + dynamic enrollmentEmpClientBranchId; + dynamic enrollmentHrId; + dynamic empClientId; + String empCodeFromHrPolcy = ''; + + List> openForEnrollmentList = []; + List> activePoliciesList = []; + + final tokenService = TokenStorageService(); + List postModules = []; + List enrollmentModules = []; + + + @override + void initState() { + super.initState(); + apiService = ApiService(context); // Initialize ApiService here + checkToken(); + // empCodeFromHrPolcy = widget.empCodeFromHrPolicy; + + // _tabController = TabController(length: 4, vsync: this); + // _tabController.addListener(() { + // setState(() { + // selectedIndex = _tabController.index; + // }); + // }); + // Future.delayed(Duration(seconds: 3), () { + // setState(() { + // isLoading = false; + // }); + // }); + } + + @override + void dispose() { + super.dispose(); + } + + checkToken() async { + // enrollToken = prefs.getString('pre_enrollment_data'); + // _postToken = prefs.getString('post_enrollment_data'); + + // Get the current token + final token = await tokenService.getCurrentToken(); + print('token - $token'); + + print('token check in'); + if ((token != null && token!.isNotEmpty)) { + print('token check done'); + _loadToken(); + } else { + print('token check reject'); + ToastHelper.showErrorToast(context, 'Session Out'); + Navigator.pushReplacementNamed(context, 'hrLogin'); + } + } + + Future _loadToken() async { + final enrollmentRaw = await tokenService.readValue('enrollmentAllowed_modules'); // "[1]" + final postRaw = await tokenService.readValue('empAllowed_modules'); // "[2,3,4]" + + // ✅ Decode safely + enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty + ? List.from(jsonDecode(enrollmentRaw)) + : []; + + postModules = postRaw != null && postRaw.isNotEmpty + ? List.from(jsonDecode(postRaw)) + : []; + + print('enrollmentModules $enrollmentModules'); + print('postModules $postModules'); + + _postPreToken = await tokenService.getCurrentToken(); + print(_postPreToken); + + if (enrollmentModules.contains(1)) { + enrollmentClient_id = await tokenService.readValue('enrollmentClient_id'); + enrollmentEmpClientBranchId = + await tokenService.readValue('enrollmentEmpClientBranchId'); + enrollmentHrId = await tokenService.readValue('enrollmentHrId'); + + await getPreCashDepositDetails(enrollmentEmpClientBranchId, + enrollmentClient_id, enrollmentHrId, _postPreToken); + } + + if (postModules.contains(2)) { + empClientId = await tokenService.readValue('empClientId'); + empClientBranchId = await tokenService.readValue('empClientBranchId'); + empHrId = await tokenService.readValue('empHrId'); + + await getPostCashDepositDetails(empClientBranchId, empClientId, empHrId, _postPreToken); + } + + } + + Future getPreCashDepositDetails(enrollmentEmpClientBranchId, + enrollmentClient_id, enrollmentHrId, _postPreToken) async { + print('IN'); + print("clintBranchId -$enrollmentEmpClientBranchId"); + print("clintID -$enrollmentClient_id"); + print("hr_id -$enrollmentHrId"); + print("token -$_postPreToken"); + + isLoading = true; + // setState(() { + // _isLoading = true; + // }); + try { + if (enrollmentEmpClientBranchId == null || enrollmentClient_id == null) { + return; + } + final response = await apiService.getCashDepositDetailsToApi( + enrollmentClient_id!, + enrollmentEmpClientBranchId!, + enrollmentHrId, + _postPreToken); + + // final response = await apiService.getCashDepositDetailsToApi( + // clintID!, clintBranchId!, hr_id, token); + print('IN1'); + if (response['status'] == 'success') { + isLoading = false; + setState(() { + print('response'); + print(response['data']); + getPreCardArrays = List>.from(response['data']); + print('getPreCardArrays'); + print(getPreCardArrays); + openForEnrollmentList = getPreCardArrays; + }); + + print('IN2'); + } else { + print('API request failed with status'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + 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; + // }); + try { + if (empClientBranchId == null || empClientId == null) { + return; + } + final response = await apiService.getActiveCashDepositDetailsToApi( + empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal); + + // final response = await apiService.getCashDepositDetailsToApi( + // clintID!, clintBranchId!, hr_id, token); + print('IN1'); + if (response['status'] == 'success') { + isLoading = false; + setState(() { + print('response'); + print(response['data']); + getPostCardArrays = List>.from(response['data']); + print('getPostCardArrays'); + print(getPostCardArrays); + + activePoliciesList = getPostCardArrays; + }); + + print('IN2'); + } else { + isLoading = false; + print('API request failed with status'); + setState(() { + activePoliciesList = []; + }); + print('API request failed with status'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + Future _showLogoutDialog() async { + return await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: Text("Confirm Logout"), + content: Text("Do you want to logout?"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text("Cancel"), + ), + TextButton( + onPressed: () => apiService.logout(), + child: Text("Logout"), + ), + ], + ), + ) ?? + false; + } + + + + Future getEcardBulkDownload(clientPolicyId) async { + try { + print('10'); + empHrId = await tokenService.readValue('empHrId'); + final response = await apiService.getEcardBulkDownloadApi(clientPolicyId,empHrId,'',_postPreToken!); + if (response['status'] == true) { + print('Request success'); + _showBulkDownloadSuccessPopup(response['message']); + } else { + ToastHelper.showErrorToast(context, response['message']); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + void _showBulkDownloadSuccessPopup(String message) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle, + color: Color(0xFF009195), + size: 60, + ), + const SizedBox(height: 16), + Text( + message, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF009195), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('OK',style: GoogleFonts.poppins( + color: Colors.white + ),), + ), + ), + ], + ), + ); + }, + ); + } + + + @override + 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, + ); + } + }, + child: buildPoliciesBody( + openEnrollment: openForEnrollmentList, + activePolicies: activePoliciesList, + ), + ), + ); + } + + Widget buildPoliciesBody({ + required List> openEnrollment, + required List> activePolicies, + }) { + return Scaffold( + body: SingleChildScrollView( + + // padding: const EdgeInsets.all(20), + child: Container( + color: const Color(0xFFF5F7F7), // 👈 same light grey as dashboard + // padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + _showLogoutDialog(); + }, + icon: const Icon( + Icons.arrow_back_ios, + size: 20, + color: Colors.black, + ), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + const SizedBox(width: 5), + Text( + 'Policies', + style: GoogleFonts.poppins( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + if(enrollmentModules.contains(1))...[ + SizedBox(height: 15), + Container( + width: double.infinity, + height: 300, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// ================= OPEN FOR ENROLLMENT ================= + const Text( + 'Open for Enrollment', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + ), + 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), + ), + child: openEnrollment.isEmpty + ? _EmptyBox('No policies open for enrollment') + : _PolicyGrid( + policies: openEnrollment, isEnrollment: true), + ), + + const SizedBox(height: 24), + ], + ), + ), + ], + if(postModules.contains(2))...[ + SizedBox(height: 20), + Container( + width: double.infinity, + height: 300, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// ================= ACTIVE POLICIES HEADER ================= + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Active Policies', + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600), + ), + _ActiveExpiredToggle( + selectedIndex: selectedIndex, + onChange: (value) async { + setState(() { + 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, + empHrId, + _postPreToken, + ); + }, + ), + ], + ), + 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), + ), + child: activePolicies.isEmpty + ? _EmptyBox('No Active policies') + : _PolicyGrid( + policies: activePolicies, + isEnrollment: false,onBulkDownload: (clientPolicyId) { + getEcardBulkDownload(clientPolicyId); + } + ), + ), + + const SizedBox(height: 10), + + const Align( + alignment: Alignment.bottomRight, + child: Text( + '* Premium may vary subject to claims', + style: TextStyle(fontSize: 10, color: Colors.red), + ), + ), + ], + ), + ), + ] + + ], + ), + )), + ); + } +} + +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( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // desktop + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: isEnrollment ? 2.8 : 2.2, + ), + 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; + + const _EnrollmentPolicyCardNew({ + required this.data, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, // 👈 pointer on hover + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, // 👈 card click + child: Container( + decoration: BoxDecoration( + color: const Color(0xFFE9F6FB), + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Policy Number + Text( + data['policy_no'] ?? '', + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + + const SizedBox(height: 4), + + /// Insurer + Text( + data['insurer_name'] ?? '', + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + + const SizedBox(height: 4), + + /// Closes On + Text( + 'Closes on: ${data['policy_expiry_date'] ?? ''}', + style: const TextStyle( + fontSize: 12, + color: Colors.red, + ), + ), + + const SizedBox(height: 10), + + /// STATUS ROW + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _StatusPillCount( + label: 'Draft', + value: data['membersCountOfDraft'] ?? 0, + color: Colors.orange, + ), + _StatusPillCount( + label: 'Enrolled', + value: data['membersCountOfEnrolled'] ?? 0, + color: Colors.blue, + ), + _StatusPillCount( + label: 'Total', + value: data['totalMembersCount'] ?? 0, + color: Colors.green, + ), + ], + ), + ], + ), + ), + ), + ); + } +} + +class _ActivePolicyCardNew extends StatelessWidget { + final Map data; + final VoidCallback? onTap; + final Function(String clientPolicyId)? onBulkDownload; + + const _ActivePolicyCardNew({required this.data, this.onTap,this.onBulkDownload,}); + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, // 👈 pointer on hover + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, // 👈 card click + child: Container( + decoration: BoxDecoration( + color: const Color(0xFFE9F6FB), + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// PREMIUM + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + 'Premium - ₹${data['total_premium'] ?? ''}*', + style: GoogleFonts.poppins( + fontSize: 12, + color: Color(0xFF009195), + fontWeight: FontWeight.w600, + ), + ), + ), + // 🔥 ICON FLOATING ABOVE CARD + if (data['is_ecard_bulk_download'] == 1) + Positioned( + top: 10, + right: 10, + child: GestureDetector( + onTap: () { + print('ICON CLICKED ${data['client_policy_id']}'); + onBulkDownload?.call( + data['client_policy_id'].toString(), + ); + }, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: const Color(0xFF009195), + borderRadius: BorderRadius.circular(6), + ), + child: const Icon( + Icons.credit_card, + color: Colors.white, + size: 16, + ), + ), + ), + ), + + + ], + ), + + const SizedBox(height: 4), + + /// POLICY NO + ICON + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + data['policy_no'] ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + + const SizedBox(height: 4), + + /// INSURER + Text( + data['insurer_name'] ?? '', + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + + const SizedBox(height: 4), + + /// DATE RANGE + Text( + '${data['policy_start_date'] ?? ''} - ${data['policy_expiry_date'] ?? ''}', + style: const TextStyle( + fontSize: 12, + color: Color(0xFF8A9B0F), + ), + ), + + const SizedBox(height: 10), + + /// ACTIVE / INACTIVE + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _StatusPillCount( + label: 'Active', + value: data['membersCountOfActive'] ?? 0, + color: Colors.green, + ), + _StatusPillCount( + label: 'Inactive', + value: data['membersCountOfInactive'] ?? 0, + color: Colors.red, + ), + ], + ), + ], + ), + ) + ) + + ); + } +} + +class _StatusPillCount extends StatelessWidget { + final String label; + final int value; + final Color color; + + const _StatusPillCount({ + required this.label, + required this.value, + required this.color, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Text( + label, + style: TextStyle(fontSize: 13, color: color), + ), + const SizedBox(width: 8), + Text( + value.toString(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ), + ); + } +} + +class _ActiveExpiredToggle extends StatelessWidget { + final int selectedIndex; + final Function(int) onChange; + + const _ActiveExpiredToggle({ + required this.selectedIndex, + required this.onChange, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: const Color(0xFFE6F4F3), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + _ToggleItem( + label: 'Active', + active: selectedIndex == 1, + onTap: () => onChange(1), + ), + _ToggleItem( + label: 'Expired', + active: selectedIndex == 2, + onTap: () => onChange(2), + ), + ], + ), + ); + } +} + +class _ToggleItem extends StatelessWidget { + final String label; + final bool active; + final VoidCallback onTap; + + const _ToggleItem({ + required this.label, + required this.active, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 6), + decoration: BoxDecoration( + color: active ? const Color(0xFF009195) : Colors.transparent, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + label, + style: TextStyle( + fontSize: 11, + color: active ? Colors.white : Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } +} + +class _EmptyBox extends StatelessWidget { + final String text; + + const _EmptyBox(this.text); + + @override + Widget build(BuildContext context) { + return Container( + height: 180, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.black12), + ), + child: Center( + child: Text(text, style: const TextStyle(color: Colors.grey)), + ), + ); + } +} diff --git a/lib/presentation/postFileUpload.dart b/lib/presentation/postFileUpload.dart new file mode 100755 index 0000000..edfcc88 --- /dev/null +++ b/lib/presentation/postFileUpload.dart @@ -0,0 +1,1220 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:jwt_decode/jwt_decode.dart'; +import 'package:nhancepolicy/customAppBar/customAppBar.dart'; +import 'dart:convert'; +import 'dart:async'; +import 'package:http/http.dart' as http; +import 'package:nhancepolicy/customAppBar/toastHelper.dart'; +import 'package:nhancepolicy/presentation/excelVerification.dart'; +import 'package:nhancepolicy/presentation/hrPolicyDetails.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; +import 'package:universal_html/html.dart' as html; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'dart:io'; +import 'package:intl/intl.dart'; +import 'package:csv/csv.dart'; + +import 'package:spreadsheet_decoder/spreadsheet_decoder.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../config/environment.dart'; +import '../customAppBar/base_layout.dart'; +import '../customAppBar/customFooter.dart'; + +class postFileUpload extends StatefulWidget { + final String ClientId; + final String policyTypeId; + final String ClientPoliyId; + final String clientBranchId; + final String Token; + final String TokenType; + final String cardType; + final String cardPolicyNo; + final String cardInsurer_name; + final String cardPolicy_name; + final String cardPolicy_ExpDate; + final String total_premium; + const postFileUpload( + {Key? key, + required this.ClientId, + required this.policyTypeId, + required this.ClientPoliyId, + required this.clientBranchId, + required this.Token, + required this.TokenType, + required this.cardType, + required this.cardPolicyNo, + required this.cardInsurer_name, + required this.cardPolicy_name, + required this.cardPolicy_ExpDate, + required this.total_premium + }) + : super(key: key); + + @override + State createState() => _postFileUploadState(); +} + +class _postFileUploadState extends State { + final tokenService = TokenStorageService(); + Uint8List? fileBytes; + Uint8List? fileBytes2; + late String _token; + dynamic getPolicyNo; + bool _isLoading = false; + dynamic getPolicyNameDetails; + dynamic clintID; + String? fileName; + int _currentStep = 0; // Step index tracker + List dataPolicy = []; + dynamic validationArray = []; + dynamic missingColumnErrorMsg = 0; + dynamic columnIndexMismatchCount = 0; + dynamic columnMissingCount = 0; + List> extractedData = []; + dynamic argumentsData; + List> originalData = []; // Original data source + List> filteredData = []; // Filtered data source + List> tableData = []; // Filtered data source + + List> nonExcelFilteredData = []; + dynamic invalidRelationships = 0; + dynamic dobAgeCheckCount = 0; + dynamic empRefId; + dynamic empPrimaryId; + dynamic empClientId; + List> getFileUploadMasterList = []; + dynamic getThrFileList = []; + late int excelValidationStaus = 1; + bool isSuccess = false; + String successContent = ''; + bool isLoading = false; + late ApiService apiService; + TextEditingController searchController = TextEditingController(); + String? _selectedOption; + final List _allowedExtensions = ['xlsx', 'xls']; + + + int _currentPage = 1; + int _rowsPerPage = 5; + + List get _paginatedData { + final startIndex = (_currentPage - 1) * _rowsPerPage; + final endIndex = + (_currentPage * _rowsPerPage).clamp(0, filteredData.length); + return filteredData.sublist(startIndex, endIndex); + } + + final List> serviceList = [ + {"id": 1, "name": "Sales"}, + {"id": 2, "name": "Service"}, + ]; + + String? selectedKey; + String? selectedValue; + String? _selectedAction; + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + _loadToken(); + getFileUploadMasterDetails(); + getFileListDetails(); + } + + @override + void dispose() { + super.dispose(); + html.window.localStorage.remove('fileBytes'); + } + + Future _loadToken() async { + // final token = prefs.getString('hrtoken'); + final token = widget.Token; + if (token != null && token.isNotEmpty) { + setState(() { + _token = token; + }); + Map? decodedToken = Jwt.parseJwt(token); + print('decodedToken $decodedToken'); + } else { + // Token is empty or null, handle accordingly (e.g., navigate to login screen) + // For now, let's navigate to the login screen + ToastHelper.showErrorToast(context, 'Session Out'); + Navigator.pushReplacementNamed(context, 'hrLogin'); + } + } + + // Future getPolicyDetails() async { + // setState(() { + // clientPolicyId = argumentsData['client_policy_id']; + // clientId = argumentsData['client_id']; + // policyType = argumentsData['type']; + // policy_name = argumentsData['policy_name']; + // }); + // } + + // Future _uploadFile1(importPolicyName) async { + // FilePickerResult? result = await FilePicker.platform.pickFiles( + // type: FileType.custom, + // allowedExtensions: ['xlsx', 'xls', 'csv'], + // ); + // + // if (result != null) { + // PlatformFile file = result.files.first; + // Uint8List fileBytes = file.bytes!; + // // Use the fileBytes as needed + // print('File name: ${file.name}'); + // print('File size: ${file.size}'); + // print('File bytes: $fileBytes'); + // _processExcelData(fileBytes); + // } else { + // // User canceled the picker + // } + // } + + Future getFileUploadMasterDetails() async { + print('9'); + try { + final response = await apiService.getFileUploadMastersToApi(widget.Token); + + if (response['status'] == true) { + print('getFileUploadMasterList1'); + setState(() { + final actions = Map.from(response['data']['actions']); + setState(() { + getFileUploadMasterList = actions.entries + .map((e) => {"key": e.key, "value": e.value}) + .toList(); + print('getFileUploadMasterList: $getFileUploadMasterList'); + }); + print('getFileUploadMasterList'); + print(getFileUploadMasterList); + }); + } else { + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + // _isLoading = false; + }); + } + } + + Future getFileListDetails() async { + empPrimaryId = await tokenService.readValue('empPrimaryId'); + empClientId = await tokenService.readValue('empClientId'); + + print('9'); + try { + final response = await apiService.getFileListToApi( + empPrimaryId, widget.cardPolicyNo, empClientId,widget.Token,widget.TokenType); + + if (response['status'] == 'success') { + print('getThrFileList'); + setState(() { + getThrFileList = List>.from(response['data']); + originalData = getThrFileList; + filteredData = List.from(originalData); + print('filteredData'); + print(filteredData); + }); + } else { + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + // _isLoading = false; + }); + } + } + + Future getHrFileDownload(id, file_name) async { + // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); + final apiurl = Environment.apiUrlPost; + final String url = '$apiurl/hrFileDownload?id=$id'; + final token = widget.Token; + + final response = await http.get( + Uri.parse(url), + headers: { + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + // 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + if (response.statusCode == 200) { + try { + print("PDF Downloaded"); + + // ✅ Create a blob from the response body bytes + final blob = html.Blob([response.bodyBytes]); + + // ✅ Generate a download URL + final url = html.Url.createObjectUrlFromBlob(blob); + + // ✅ Trigger file download automatically + final anchor = html.AnchorElement(href: url) + ..setAttribute('download', '$file_name') + ..click(); + + // ✅ Revoke the URL to free memory + html.Url.revokeObjectUrl(url); + + ToastHelper.showSuccessToast(context, 'File Downloaded Successfully'); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + ToastHelper.showErrorToast(context, 'Failed to download'); + print("Download failed with status: ${response.statusCode}"); + } + } + + void _uploadFile() async { + print('Test'); + if (kIsWeb) { + print('kIsWeb'); + final input = html.FileUploadInputElement(); + input.accept = '.xlsx,.xls'; + input.click(); + input.onChange.listen((event) async { + final file = input.files?.first; + if (file == null) return; + + final fileExtension = file.name.split('.').last.toLowerCase(); + + // ❌ INVALID FORMAT + if (!_allowedExtensions.contains(fileExtension)) { + ToastHelper.showErrorToast( + context, + 'File format not supported. Please upload XLSX or XLS', + ); + + setState(() { + fileName = null; + resetErrorCount(); + // html.window.localStorage.remove('fileBytes'); + }); + + return; // 🚫 STOP HERE + } + + final reader = html.FileReader(); + reader.readAsArrayBuffer(file); + await reader.onLoadEnd.first; // Wait for the file to be loaded + if (reader.readyState == html.FileReader.DONE) { + Uint8List? fileBytes = reader.result as Uint8List?; + if (fileBytes != null) { + setState(() { + fileName = file.name; + }); + // Save fileBytes to local storage + final jsonString = json.encode(fileBytes); + html.window.localStorage['fileBytes'] = jsonString; + print('File Name: $fileName'); + print('File Bytes: $fileBytes'); + sendExcelFIleTOAPI(fileBytes, fileName); + // Call the function to process Excel data here + // _processExcelData(fileBytes, fileName); + } + } + }); + } else { + // Handle non-web platforms here (e.g., show an error message) + print('File upload is only supported on web platforms.'); + } + } + + int countInvalidDobs(List> data) { + int invalidCount = 0; + + for (var entry in data) { + DateTime dob; + + // if (entry['DOB'] is String) { + dob = DateTime.parse(entry['DOB'].toString()); + // } else if (entry['DOB'] is DateTime) { + // dob = entry['DOB']; + // } else { + // // Invalid DOB format, skip this entry + // continue; + // } + print(entry['Relation']); + if ((entry['Relation'].toString() == 'Son' || + entry['Relation'].toString() == 'Daughter')) { + if (DateTime.now().difference(dob).inDays > 25 * 365) { + print('child $dob'); + invalidCount++; + } + } else if ((entry['Relation'] != 'Son' && + entry['Relation'] != 'Daughter')) { + if (DateTime.now().difference(dob).inDays < 18 * 365) { + print('others $dob'); + invalidCount++; + } + } + } + + return invalidCount; + } + + void _dragAndDropFile(html.File file) async { + print('file'); + print(file); + // Prepare form data + final formData = html.FormData(); + formData.appendBlob('file', file); + + // Send formData to API endpoint + final response = await html.HttpRequest.request( + 'your_api_endpoint_here', + method: 'POST', + sendData: formData, + ); + + // Handle response as needed + print(response.responseText); + } + + Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { + empPrimaryId = await tokenService.readValue('empPrimaryId'); + // Future.delayed(Duration(seconds: 3), () { + setState(() { + isLoading = true; + }); + // }); + print('submit'); + print(fileBytes); + if (fileBytes == null) { + ToastHelper.showErrorToast(context, 'Please upload file'); + print('return'); + return; // No file selected + } else { + print('else'); + // // Prepare form data + // final formData = html.FormData(); + // formData.appendBlob('file', html.Blob([fileBytes]), fileName); + + // URL of the API where you want to send the file + final apiUrl = Environment.apiUrlPost + 'hrFileUpload'; + print('else'); + // Create a multipart request + final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); + print('else'); + // Attach the file to the request + // Set authorization token in headers + request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; + request.headers['Authorization'] = 'Bearer $_token'; + // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, + // filename: fileName)); + print('Filename: $fileName'); + // request.files.add(http.MultipartFile.fromBytes( + // 'file', + // fileBytes, + // filename: fileName ?? 'default_filename.xlsx', + // )); + request.files.add(http.MultipartFile.fromBytes( + 'file_name', + fileBytes, + filename: fileName ?? 'default_filename.xlsx', + )); + print('clintID: $clintID'); + + request.fields['client_id'] = widget.ClientId; + request.fields['policy_no'] = widget.cardPolicyNo; + request.fields['client_branch_id'] = widget.clientBranchId; + request.fields['file_action'] = selectedKey!; + // request.fields['status'] = selectedKey!; + request.fields['created_by'] = empPrimaryId; + request.fields['policy_id'] = widget.ClientPoliyId; + // "client_id": 1, + // "client_branch_id": 2, + // "policy_no": "POL123456", + // "file_action": , + // "status": "inception", + // "created_by": 10 + + print('request : $request'); + // Send the request + final response = await request.send(); + print('else'); + // Read response stream as a string + final responseString = await response.stream.bytesToString(); + print(responseString); + Map data = json.decode(responseString); + if (data['status'] == true) { + print('upload success'); + setState(() { + isLoading = false; + }); + ToastHelper.showSuccessToast(context, data['message']); + getFileListDetails(); + setState(() { + selectedValue = null; + selectedKey = null; + resetErrorCount(); + }); + } else { + getFileListDetails(); + setState(() { + isLoading = false; + selectedValue = null; + selectedKey = null; + resetErrorCount(); + }); + ToastHelper.showErrorToast(context, data['message']); + } + } + } + + resetErrorCount() { + setState(() { + fileBytes = null; + fileName = null; + }); + } + + void search(String query) { + print(query); + // Check if the query is empty + if (query.isEmpty) { + // If search query is empty, show all data + setState(() { + filteredData = List.from(originalData); + }); + } else { + // Filter the original data based on the search query + setState(() { + filteredData = originalData.where((row) { + // Implement your filter logic here + // For example, check if any field in the row contains the query + // Adjust this logic based on your data structure + return row['file_name'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['file_action'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()) || + row['created_at'] + .toString() + .toLowerCase() + .contains(query.toLowerCase()); + }).toList(); + }); + } + print(filteredData.length); + } + + @override + Widget build(BuildContext context) { + return BaseLayout( + child: _buildContent(context), + ); + } + + Widget _buildContent(BuildContext context) { + return isLoading ? Container( + color: Colors.transparent, // Semi-transparent background + child: Center( + child: // Your GIF loader widget + Image.asset( + height: 60, + width: 60, + 'assets/nhance-loader.gif'), // Adjust path to your GIF loader + ), + ): Container( + child: Column( + children: [ + 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, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height:20), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Select File Action + Expanded( + flex: 5, + child: buildStyledDropdown( + label: 'Select File Action', + value: selectedKey, + items: getFileUploadMasterList, + onChanged: (val) { + setState(() { + selectedKey = val; + selectedValue = getFileUploadMasterList + .firstWhere((e) => e['key'] == val)['value']; + }); + }, + ), + ), + + const SizedBox(width: 16), + + /// Upload Box + Expanded( + flex: 5, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// ✅ LABEL + RichText( + text: TextSpan( + text: 'Upload File', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + children: const [ + TextSpan( + text: '(Supported Formats: XLSX)', + style: TextStyle( + fontSize: 11, + color: Colors.grey, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + + const SizedBox(height: 6), + + /// ✅ DOTTED UPLOAD BOX + DragTarget( + onAccept: (html.File droppedFile) { + setState(() { + fileName = droppedFile.name; + }); + _dragAndDropFile(droppedFile); + }, + builder: (context, candidateData, rejectedData) { + return GestureDetector( + onTap: () { + if (selectedValue != null) { + _uploadFile(); + } else { + ToastHelper.showErrorToast( + context, + 'Please select file action', + ); + } + }, + child: Container( + height: 40 , + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFF00A6A6), + width: 1, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + fileName ?? 'Upload Your Documents', + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 13, + color: fileName == null + ? Colors.grey + : Colors.black, + ), + ), + ), + const Icon( + Icons.file_upload_outlined, + size: 18, + color: Colors.black, + ), + ], + ), + ) + ); + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: Column( + children: [ + _buildFileUploadedGrid(), + const SizedBox(height: 16), + _buildPagination(context), + ], + ), + ) + + ], + ), + ], + ), + ); + + + } + + Widget buildUploadBox({ + required VoidCallback onTap, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Upload File (Supported Formats: XLSX)', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 6), + GestureDetector( + onTap: onTap, + child: Container( + height: 42, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFF00A6A6), + style: BorderStyle.solid, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + 'Upload Your Documents', + style: GoogleFonts.poppins( + fontSize: 13, + color: Colors.grey[600], + ), + ), + ), + const Icon( + Icons.upload_file, + size: 18, + color: Color(0xFF00A6A6), + ), + ], + ), + ), + ), + ], + ); + } + + + + Widget buildStyledDropdown({ + required String label, + required String? value, + required List> items, + required Function(String?) onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 6), + Container( + height: 42, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + // border: Border.all(color: const Color(0xFFE0E0E0)), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: value, + hint: Text( + 'Select', + style: GoogleFonts.poppins(fontSize: 13), + ), + icon: const Icon(Icons.keyboard_arrow_down), + items: items.map((item) { + return DropdownMenuItem( + value: item['key'], + child: Text( + item['value'], + style: GoogleFonts.poppins(fontSize: 13), + ), + ); + }).toList(), + onChanged: onChanged, + ), + ), + ), + ], + ); + } + + + Widget _buildFileUploadedGrid() { + if (filteredData.isEmpty) { + return const SizedBox( + height: 120, + child: Center(child: Text('No uploaded files')), + ); + } + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 👈 2 cards per row + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 10, // 👈 card height + ), + itemCount: _paginatedData.length, + itemBuilder: (context, index) { + final item = _paginatedData[index]; + return _buildFileCard(item); + }, + ); + } + + Widget _buildFileCard(Map item) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + decoration: BoxDecoration( + color: const Color(0xFFEFF9FA), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFF9AD6DB)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + /// 📄 File Icon + Container( + height: 44, + width: 44, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF00A6A6)), + ), + child: const Icon( + Icons.description_outlined, + color: Color(0xFF00A6A6), + size: 22, + ), + ), + + const SizedBox(width: 12), + + /// 📑 LEFT CONTENT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Row 1 → File name + Text( + item['file_name'] ?? '-', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + color: const Color(0xFF101010), + ), + ), + + const SizedBox(height: 4), + + /// Row 2 → Action - Date + RichText( + text: TextSpan( + style: GoogleFonts.poppins(fontSize: 11), + children: [ + TextSpan( + text: item['file_action'] ?? '', + style: const TextStyle( + color: Color(0xFF00999E), + fontWeight: FontWeight.w500, + ), + ), + const TextSpan( + text: ' - ', + style: TextStyle(color: Color(0xFF585858)), + ), + TextSpan( + text: formatDate(item['created_at']), + style: const TextStyle(color: Color(0xFF585858)), + ), + ], + ), + ), + ], + ), + ), + + /// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD) + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + /// 🔴 Error + Status + Row( + children: [ + + ], + ), + + const SizedBox(height: 8), + + Row( + children: [ + if (item['file_error_status'] == '1') + InkWell( + onTap: () async { + print(item); + // return; + final String? token = await tokenService.getCurrentToken(); + final String? empClientId = await tokenService.readValue('empClientId'); + final String? empBranchId = await tokenService.readValue('empClientBranchId'); + + print(item); + print(empClientId); + print(widget.policyTypeId); + print(empBranchId); + print(token); + print('post'); + print(widget.cardType); + print(widget.cardPolicyNo); + print(widget.cardInsurer_name); + print(widget.cardPolicy_name); + print(widget.cardPolicy_ExpDate); + print(item['id']); + + // ✅ SAFETY CHECK + if (token == null || + empClientId == null || + empBranchId == null) { + debugPrint('❌ Missing required data for navigation ${token}'); + return; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + excelErrorScreen( + ClientId: empClientId, + policy_no: item['policy_no'], + action: item['file_action'], + created_at: item['created_at'], + clientBranchId: empBranchId, + Token: token, + TokenType: 'post', + id: item['id'] + ), + ), + ); + }, + child: Icon( + Icons.error, + size: 16, + color: Colors.red, + ), + ), + SizedBox(width: 10), + _buildStatusChip(item['status']), + SizedBox(width: 10), + InkWell( + onTap: () { + getHrFileDownload(item['id'], item['file_name']); + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: Color(0xFFC5F2F4), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF76CED2)), + ), + child: Icon( + Icons.file_download_outlined, + color: Color(0xFF1D1B20), + size: 22, + ), + ), + ), + ], + ), + /// ⬇ Download + + ], + ), + ], + ), + ); + } + + + Widget _buildStatusChip(String status) { + final s = status.toLowerCase(); + + Color bg; + + if (s == 'success') { + bg = const Color(0xFF94E9B8); + } else if (s == 'failed') { + bg = const Color(0xFFFDC2C2); + } else if (s.contains('progress')) { + bg = const Color(0xFFFFE8AC); + } else { + bg = const Color(0xFFFBBF24); + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + status, + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ); + } + + + static final _dataBold = GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Color(0xFF000000), + ); + + static final _dataSub = GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w300, + color: Color(0xFF585757), + ); + + static const _headerStyle = TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ); + + 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()), + ), + ); + } + + String formatDate(String? dateString) { + if (dateString == null || dateString.isEmpty) return '-'; + + try { + DateTime parsedDate = DateTime.parse(dateString); + return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); + } catch (e) { + return '-'; + } + } +} diff --git a/lib/presentation/preFileUpload.dart b/lib/presentation/preFileUpload.dart new file mode 100755 index 0000000..a430ad8 --- /dev/null +++ b/lib/presentation/preFileUpload.dart @@ -0,0 +1,1544 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:jwt_decode/jwt_decode.dart'; +import 'dart:convert'; +import 'dart:async'; +import 'package:http/http.dart' as http; +import 'package:nhancepolicy/customAppBar/toastHelper.dart'; +import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; +import 'package:universal_html/html.dart' as html; +import 'package:flutter/foundation.dart' show kIsWeb; +// import 'package:excel/excel.dart'; +import 'package:excel/excel.dart' hide Border,TextSpan; +import 'dart:io'; +import 'package:intl/intl.dart'; +import 'package:csv/csv.dart'; +import '../config/environment.dart'; +import '../customAppBar/base_layout.dart'; +import 'excelVerification.dart'; + +class preFileUpload extends StatefulWidget { + final String ClientId; + final String policyTypeId; + final String ClientPoliyId; + final String clientBranchId; + final String Token; + final String TokenType; + final String cardType; + final String cardPolicyNo; + final String cardInsurer_name; + final String cardPolicy_name; + final String cardPolicy_ExpDate; + final String total_premium; + const preFileUpload( + {Key? key, + required this.ClientId, + required this.policyTypeId, + required this.ClientPoliyId, + required this.clientBranchId, + required this.Token, + required this.TokenType, + required this.cardType, + required this.cardPolicyNo, + required this.cardInsurer_name, + required this.cardPolicy_name, + required this.cardPolicy_ExpDate, + required this.total_premium, + + }) + : super(key: key); + + @override + State createState() => _excelVerifyState(); +} + +class _excelVerifyState extends State { + final tokenService = TokenStorageService(); + Uint8List? fileBytes; + Uint8List? fileBytes2; + late String _token; + dynamic getPolicyNo; + bool _isLoading = false; + dynamic getPolicyNameDetails; + String? fileName; + int _currentStep = 0; // Step index tracker + List dataPolicy = []; + dynamic validationArray = []; + dynamic missingColumnErrorMsg = 0; + dynamic columnIndexMismatchCount = 0; + dynamic columnMissingCount = 0; + List> extractedData = []; + dynamic argumentsData; + List> originalData = []; // Original data source + List> filteredData = []; // Filtered data source + List> tableData = []; // Filtered data source + + List> nonExcelFilteredData = []; + dynamic invalidRelationships = 0; + dynamic dobAgeCheckCount = 0; + dynamic empRefId; + List excelHeader = []; + List>> excelData = []; + late int excelValidationStaus = 1; + bool isSuccess = false; + String successContent = ''; + bool isLoading = false; + late ApiService apiService; + final TextEditingController openDateController = TextEditingController(); + final TextEditingController closeDateController = TextEditingController(); + dynamic getThrFileList = []; + final List _allowedExtensions = ['xlsx', 'xls']; + + int _currentPage = 1; + int _rowsPerPage = 5; + + List get _paginatedData { + final startIndex = (_currentPage - 1) * _rowsPerPage; + final endIndex = + (_currentPage * _rowsPerPage).clamp(0, filteredData.length); + return filteredData.sublist(startIndex, endIndex); + } + + @override + void initState() { + super.initState(); + apiService = ApiService(context); + getFileListDetails(); + _loadToken(); + } + + @override + void dispose() { + super.dispose(); + html.window.localStorage.remove('fileBytes'); + } + + Future _loadToken() async { + final token = widget.Token; + if (token != null && token.isNotEmpty) { + setState(() { + _token = token; + }); + Map? decodedToken = Jwt.parseJwt(token); + print('decodedToken $decodedToken'); + } else { + // Token is empty or null, handle accordingly (e.g., navigate to login screen) + // For now, let's navigate to the login screen + ToastHelper.showErrorToast(context, 'Session Out'); + Navigator.pushReplacementNamed(context, 'hrLogin'); + } + } + + // Future getPolicyDetails() async { + // setState(() { + // clientPolicyId = argumentsData['client_policy_id']; + // clientId = argumentsData['client_id']; + // policyType = argumentsData['type']; + // policy_name = argumentsData['policy_name']; + // }); + // } + + // Future _uploadFile1(importPolicyName) async { + // FilePickerResult? result = await FilePicker.platform.pickFiles( + // type: FileType.custom, + // allowedExtensions: ['xlsx', 'xls', 'csv'], + // ); + // + // if (result != null) { + // PlatformFile file = result.files.first; + // Uint8List fileBytes = file.bytes!; + // // Use the fileBytes as needed + // print('File name: ${file.name}'); + // print('File size: ${file.size}'); + // print('File bytes: $fileBytes'); + // _processExcelData(fileBytes); + // } else { + // // User canceled the picker + // } + // } + + void _uploadFile(importPolicyName) async { + print('Test'); + if (kIsWeb) { + print('kIsWeb'); + final input = html.FileUploadInputElement(); + input.accept = '.xlsx,.xls'; + input.click(); + input.onChange.listen((event) async { + final file = input.files?.first; + if (file == null) return; + + final fileExtension = file.name.split('.').last.toLowerCase(); + + // ❌ INVALID FORMAT + if (!_allowedExtensions.contains(fileExtension)) { + ToastHelper.showErrorToast( + context, + 'File format not supported. Please upload XLSX or XLS', + ); + + resetErrorCount(); + + return; // 🚫 STOP HERE + } + final reader = html.FileReader(); + reader.readAsArrayBuffer(file); + await reader.onLoadEnd.first; // Wait for the file to be loaded + if (reader.readyState == html.FileReader.DONE) { + Uint8List? fileBytes = reader.result as Uint8List?; + if (fileBytes != null) { + setState(() { + fileName = file.name; + }); + // Save fileBytes to local storage + // final jsonString = json.encode(fileBytes); + // html.window.localStorage['fileBytes'] = jsonString; + print('File Name: $fileName'); + print('File Bytes: $fileBytes'); + sendExcelFIleTOAPI(fileBytes, fileName); + // Call the function to process Excel data here + // _processExcelData(fileBytes, fileName); + } + } + }); + } else { + // Handle non-web platforms here (e.g., show an error message) + print('File upload is only supported on web platforms.'); + } + } + + bool _validateDatesBeforeUpload() { + if (openDateController.text.isEmpty || + closeDateController.text.isEmpty) { + ToastHelper.showErrorToast2(context,'','Please select both Enrolment Open Date and Close Date'); + return false; + } + return true; + } + + + void _processExcelData(Uint8List fileBytes, fileName) { + List> dataArray; + if (fileName.endsWith('.xlsx')) { + dataArray = decodeExcelData(fileBytes); + } else if (fileName.endsWith('.xls')) { + dataArray = decodeXLSData(fileBytes); + } else if (fileName.endsWith('.csv')) { + print('csv'); + dataArray = decodeCSVData(fileBytes); + } else { + throw UnsupportedError('Unsupported file format: $fileName'); + } + + print('_processExcelData'); + // Decode the Excel file and extract relevant data + // Assuming dataArray is your array containing Excel data + // List> dataArray = decodeExcelData(fileBytes); + print(dataArray); + // print(dataArray[0].toString()); + // Extract Name, Age, and City from the array + print(dataArray[0].length); + + if (dataArray[0].length == 11) { + for (int i = 0; i < dataArray.length; i++) { + Map dataMap = { + "Sno": dataArray[i][0].value, + "Emp_Code": dataArray[i][1].value, + "Name": dataArray[i][2].value, + "DOJ": dataArray[i][3].value, + "Gender": dataArray[i][4].value, + "Relation": dataArray[i][5].value, + "DOB": dataArray[i][6].value, + "Mail": dataArray[i][7].value, + "Mobile": dataArray[i][8].value, + "SI": dataArray[i][9].value, + "Grade": dataArray[i][10].value, + }; + if (i == 0) { + // print(dataMap); + validationArray.add(dataMap); + } else { + // print(dataMap); + extractedData.add(dataMap); + } + } + // Do something with extracted data (e.g., display in UI) + originalData = extractedData; + filteredData = List.from(originalData); + print('filteredData'); + print(filteredData); + print(validationArray); + + validationArray[0].forEach((key, value) { + print(key); + print(value); + if (key.toString().trim().toLowerCase() != + value.toString().trim().toLowerCase()) { + // If key and value are not equal, increment mismatch count + columnIndexMismatchCount++; + print('columnIndexMismatchCount: $value'); + } + if (value.toString() == 'null') { + // If value is null, increment missing count + columnMissingCount++; + print('Value: $value'); + } + }); + + print('columnIndexMismatchCount: $columnIndexMismatchCount'); + print('columnMissingCount: $columnMissingCount'); + + nonExcelFilteredData = filteredData.where((item) { + final relation = item['Relation']; + return relation != null && + relation.toString().trim().toLowerCase() != 'self'; + }).toList(); + print('nonExcelFilteredData'); + + print(nonExcelFilteredData); + print(nonExcelFilteredData.length); + if (argumentsData['type'] == 'GPA') { + if (nonExcelFilteredData.length > 0) { + print('nonSelf'); + invalidRelationships = nonExcelFilteredData.length; + } else { + print('Self'); + invalidRelationships = nonExcelFilteredData.length; + } + print('invalidRelationships: $invalidRelationships'); + } else { + invalidRelationships = 0; + } + + int invalidDobCount = countInvalidDobs(filteredData); + + print('Number of invalid DOBs: $invalidDobCount'); + + dobAgeCheckCount = invalidDobCount; + } else { + print('Some Column is Missing'); + var columnMissingCount = 11 - dataArray[0].length; + missingColumnErrorMsg = columnMissingCount; + print(missingColumnErrorMsg); + } + } + + // bool checkAllSelf(List> dataList) { + // // Check if any value of 'Relation' key is not 'Self' + // bool allSelf = dataList.every((data) => data['Relation'] == 'Self'); + // // If all values are 'Self', return true; otherwise, return false + // return allSelf; + // } + + // Placeholder function for decoding Excel data + List> decodeExcelData(Uint8List fileBytes) { + print('decodeExcelData'); + + // Create an Excel instance from the fileBytes + final excel = Excel.decodeBytes(fileBytes); + print('decodeExcelData'); + print(excel); + + // Assuming there's only one sheet in the Excel file + final sheet = excel.tables.keys.first; + final table = excel.tables[sheet]!; + + // Convert Excel table to a List> + // Convert Excel table to a List> + List> dataArray = []; + for (int rowIdx = 0; rowIdx < table.rows.length; rowIdx++) { + List rowData = []; + for (int colIdx = 0; colIdx < table.rows[rowIdx].length; colIdx++) { + var value = table.rows[rowIdx][colIdx]?.value; + rowData.add(Data(value, rowIdx, colIdx, sheet)); + } + dataArray.add(rowData); + } + return dataArray; + } + + List> decodeXLSData(Uint8List fileBytes) { + final Excel excelData = Excel.decodeBytes(fileBytes); + final sheet = excelData.tables.keys.first; + final table = excelData.tables[sheet]!; + List> dataArray = []; + for (int rowIdx = 0; rowIdx < table.rows.length; rowIdx++) { + List rowData = []; + for (int colIdx = 0; colIdx < table.rows[rowIdx].length; colIdx++) { + var value = table.rows[rowIdx][colIdx]?.value; + rowData.add(Data(value, rowIdx, colIdx, sheet)); + } + dataArray.add(rowData); + } + return dataArray; + } + + List> decodeCSVData(Uint8List fileBytes) { + String csvString = utf8.decode(fileBytes); + List> csvData = const CsvToListConverter().convert(csvString); + List> dataArray = []; + + // Skip the header row (if it exists) and start from index 1 + for (int i = 1; i < csvData.length; i++) { + List rowData = []; + for (int j = 0; j < csvData[i].length; j++) { + // Assuming the CSV data is of type String + rowData.add(Data(csvData[i][j].toString(), i, j, 'Sheet1')); + } + dataArray.add(rowData); + } + return dataArray; + } + + int countInvalidDobs(List> data) { + int invalidCount = 0; + + for (var entry in data) { + DateTime dob; + + // if (entry['DOB'] is String) { + dob = DateTime.parse(entry['DOB'].toString()); + // } else if (entry['DOB'] is DateTime) { + // dob = entry['DOB']; + // } else { + // // Invalid DOB format, skip this entry + // continue; + // } + print(entry['Relation']); + if ((entry['Relation'].toString() == 'Son' || + entry['Relation'].toString() == 'Daughter')) { + if (DateTime.now().difference(dob).inDays > 25 * 365) { + print('child $dob'); + invalidCount++; + } + } else if ((entry['Relation'] != 'Son' && + entry['Relation'] != 'Daughter')) { + if (DateTime.now().difference(dob).inDays < 18 * 365) { + print('others $dob'); + invalidCount++; + } + } + } + + return invalidCount; + } + + void _dragAndDropFile(html.File file) async { + print('file'); + print(file); + // Prepare form data + final formData = html.FormData(); + formData.appendBlob('file', file); + + // Send formData to API endpoint + final response = await html.HttpRequest.request( + 'your_api_endpoint_here', + method: 'POST', + sendData: formData, + ); + + // Handle response as needed + print(response.responseText); + } + + void _retrieveAndUploadFile() { + final jsonString = html.window.localStorage['fileBytes']; + if (jsonString != null) { + final decodedBytes = json.decode(jsonString); + if (decodedBytes is List) { + setState(() { + fileName = fileName ?? + 'Retrieved File'; // Provide a default name if fileName is null + }); + sendExcelFIleTOAPI( + Uint8List.fromList(decodedBytes.cast()), + fileName!, + ); + } + } + } + + Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { + + // Future.delayed(Duration(seconds: 3), () { + // setState(() { + isLoading = true; + // }); + // }); + print('submit'); + print(fileBytes); + if (fileBytes == null) { + print('return'); + return; // No file selected + } else { + print('else'); + // // Prepare form data + // final formData = html.FormData(); + // formData.appendBlob('file', html.Blob([fileBytes]), fileName); + final enrollmentHrId = await tokenService.readValue('enrollmentHrId'); + // URL of the API where you want to send the file + final apiUrl = Environment.apiUrl + 'employeeUpload'; + print('else'); + // Create a multipart request + final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); + print('else'); + // Attach the file to the request + // Set authorization token in headers + request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; + request.headers['Authorization'] = 'Bearer $_token'; + // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, + // filename: fileName)); + print('Filename: $fileName'); + request.files.add(http.MultipartFile.fromBytes( + 'file', + fileBytes, + filename: fileName ?? 'default_filename.xlsx', + )); + + request.fields['client_id'] = widget.ClientId; + // if (policyFirstPart == 'GPA') { + request.fields['policy_id'] = widget.ClientPoliyId; + request.fields['client_branch_id'] = widget.clientBranchId; + request.fields['enrollment_open_date'] = openDateController.text; + request.fields['enrollment_close_date'] = closeDateController.text; + request.fields['created_by'] = enrollmentHrId!; + + // } else { + // request.fields['policy_id'] = '3'; + // } + print('request : $request'); + // Send the request + final response = await request.send(); + print('else'); + // Read response stream as a string + final responseString = await response.stream.bytesToString(); + print('else'); + // Check the status code of the response + if (response.statusCode == 200) { + isLoading = false; + Map data = json.decode(responseString); + if (data['status'] == false) { + ToastHelper.showSuccessToast(context, data['message']); + print('Table'); + + setState(() { + isSuccess = true; + successContent = data['message']; + excelValidationStaus = 0; + }); + resetErrorCount(); + handleImportAction(); + getFileListDetails(); + } else { + setState(() { + isLoading = false; + }); + handleImportAction(); + ToastHelper.showErrorToast2(context,"",data['message']); + resetErrorCount(); + getFileListDetails(); + // ToastHelper.showErrorToast(context, data['message']); + print('Table'); + } + } else { + setState(() { + isLoading = false; + }); + // ToastHelper.showSuccessToast( + // context, 'Failed to upload file: ${response.reasonPhrase}'); + ToastHelper.showErrorToast(context, 'Something went wrong'); + print('Failed to upload file: ${response.reasonPhrase}'); + } + } + } + + Future handleImportAction() async { + print('handleImportAction'); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); + var activity = "import_enrollempdata"; + + dynamic response; + + print('postId - $postId'); + print('preId - $preId'); + print('activity - $activity'); + + try { + print('10'); + + response = await apiService.getImportLogHrActivity( + postId!, preId!, widget.Token, activity); + + if (response['status'] == 'success') { + print('Request success'); + } else { + // ToastHelper.showWarningToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + print('Exception occurred: $e'); + } + } + + Future getFileListDetails() async { + final enrollmentPrimaryId = await tokenService.readValue('enrollmentEmpPrimaryId'); + final enrollmentClientId = await tokenService.readValue('enrollmentClient_id'); + + print('9'); + try { + final response = await apiService.getFileListToApi( + enrollmentPrimaryId, widget.cardPolicyNo, enrollmentClientId,widget.Token,widget.TokenType); + + if (response['status'] == true) { + print('getThrFileList'); + setState(() { + getThrFileList = List>.from(response['data']); + originalData = getThrFileList; + filteredData = List.from(originalData); + print('filteredData'); + print(filteredData); + }); + } else { + print('Request failed with status: ${response['code']}'); + } + } catch (e) { + setState(() { + isLoading = false; + }); + print('Exception occurred: $e'); + } finally { + setState(() { + // _isLoading = false; + }); + } + } + + void search(String query) { + setState(() { + if (query.isEmpty) { + // If search query is empty, show all data + filteredData = List.from(originalData); + } else { + // Filter the data based on the search query + filteredData = originalData.where((item) { + // Implement your filter logic here, for example: + return item['Emp_Code'].toLowerCase().contains(query.toLowerCase()); + }).toList(); + } + }); + } + + downloadSampleFile() { + final anchor = html.AnchorElement(href: 'assets/assets/Template_File.xlsx'); + anchor.download = 'Template_File.xlsx'; // Set the filename + anchor.click(); // Trigger a click on the anchor element + } + + // Future downloadSampleFile() async { + // + // final response = await apiService.getSampleFileDownload(widget.Token); + // print('check 1'); + // if (response['status'] == 'success') { + // final url = response['data']; + // _launchURL(url); + // } else { + // ToastHelper.showErrorToast(context, '⚠️ Unknown response format'); + // print('⚠️ Unknown response format'); + // } + // } + // + // Future _launchURL(String url) async { + // print('url $url'); + // try { + // final Uri uri = Uri.parse(url); + // await launchUrl(uri, mode: LaunchMode.externalApplication); + // } catch (e) { + // print('Could not launch URL: $e'); + // } + // } + + resetErrorCount() { + setState(() { + fileBytes = null; + fileName = null; + }); + } + + Future getHrFileDownload(id, file_name) async { + // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); + final apiurl = Environment.apiUrl; + final String url = '$apiurl/hrFileDownload?id=$id'; + final token = widget.Token; + + final response = await http.get( + Uri.parse(url), + headers: { + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + // 'app-signature': 'ts-traveltool-2025-signature-123456', + }, + ); + + if (response.statusCode == 200) { + try { + print("PDF Downloaded"); + + // ✅ Create a blob from the response body bytes + final blob = html.Blob([response.bodyBytes]); + + // ✅ Generate a download URL + final url = html.Url.createObjectUrlFromBlob(blob); + + // ✅ Trigger file download automatically + final anchor = html.AnchorElement(href: url) + ..setAttribute('download', '$file_name') + ..click(); + + // ✅ Revoke the URL to free memory + html.Url.revokeObjectUrl(url); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + print("Download failed with status: ${response.statusCode}"); + } + } + + @override + Widget build(BuildContext context) { + return BaseLayout( + child: _buildContent(context), + ); + } + + Widget _buildContent(BuildContext context) { + return isLoading ? Container( + color: Colors.transparent, // Semi-transparent background + child: Center( + child: // Your GIF loader widget + Image.asset( + height: 60, + width: 60, + 'assets/nhance-loader.gif'), // Adjust path to your GIF loader + ), + ) : Container( + // padding: const EdgeInsets.all(20), + // color: Color(0xFFEFF3F6), + child: Column( + children: [ + 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, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + Row( + children: [ + SizedBox( + width: 260, // 👈 set your required width + child: _dateField( + label: 'Enrolment Open Date', + controller: openDateController, + onTap: () async { + final picked = await showDatePicker( + context: context, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + initialDate: DateTime.now(), + ); + if (picked != null) { + openDateController.text = + DateFormat('dd-MM-yyyy').format(picked); + } + }, + ), + ), + const SizedBox(width: 16), + SizedBox( + width: 260, // 👈 same width + child: _dateField( + label: 'Enrolment Close Date', + controller: closeDateController, + onTap: () async { + final picked = await showDatePicker( + context: context, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + initialDate: DateTime.now(), + ); + if (picked != null) { + closeDateController.text = + DateFormat('dd-MM-yyyy').format(picked); + } + }, + ), + ), + ], + ), + SizedBox(height: 20), + Row( + children: [ + Text( + 'Upload File', + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + SizedBox(height: 5), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + alignment: Alignment.center, + height: 125, + decoration: BoxDecoration( + color: Color(0xFFF7F5F6), // ✅ moved here + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFF00A6A6), + width: 1, + ), + ), + child: GestureDetector( + onTap: () { + if (!_validateDatesBeforeUpload()) return; + if (fileName == null) { + _uploadFile('Policy Name'); // ✅ same function + } + }, + child: DragTarget( + onAccept: (html.File droppedFile) { + if (!_validateDatesBeforeUpload()) return; + + setState(() { + fileName = droppedFile.name; + }); + _dragAndDropFile(droppedFile); + }, + builder: ( + BuildContext context, + List candidateData, + List rejectedData, + ) { + return Container( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + fileName != null + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 40, + height: 40 , + child: ElevatedButton( + onPressed: () => null, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD4F1F2), + elevation: 0, + padding: EdgeInsets.zero, // ✅ IMPORTANT + alignment: Alignment.center, // ✅ FORCE CENTER + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide( // ✅ BORDER ADDED + color: Color(0xFF00999E), + width: 1, + ), + ), + ), + child: Icon( + Icons.file_upload_outlined, + size: 22, + color: Color(0xFF00999E), + ) + ), + ), + const SizedBox(height: 15), + Text( + fileName!, + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 15), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: resetErrorCount, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.delete_forever, + size: 20, + color: Colors.red, + ), + SizedBox(width: 4), + Text( + 'Remove', + style: TextStyle( + fontSize: 13, + color: Color(0xFF727272), + ), + ), + ], + ), + ), + ), + ], + ) + : Column( + children: [ + SizedBox( + width: 40, + height: 40 , + child: ElevatedButton( + onPressed: () { + if (!_validateDatesBeforeUpload()) return; + if (fileName == null) { + _uploadFile('Policy Name'); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD4F1F2), + elevation: 0, + padding: EdgeInsets.zero, // ✅ IMPORTANT + alignment: Alignment.center, // ✅ FORCE CENTER + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide( // ✅ BORDER ADDED + color: Color(0xFF00999E), + width: 1, + ), + ), + ), + child: Icon( + Icons.file_upload_outlined, + size: 22, + color: Color(0xFF00999E), + ) + ), + ), + SizedBox(height: 12), + Text('Upload Your Documents', + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF000000) + ), + ), + SizedBox(height: 8), + Text( + '(Supported Format: XLSX)', + style: GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Color(0xFF707070) + ), + ), + ], + ), + ], + ), + ); + }, + ), + ), + ), + ), + ], + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: + MainAxisAlignment + .start, + children: [ + Text( + 'Please download the sample file to review the format.', + textAlign: + TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: + FontWeight.w400, + color: Color(0xFF707070) + )), + MouseRegion( + cursor: SystemMouseCursors + .click, + child: GestureDetector( + onTap: () { + downloadSampleFile(); + }, + child: Text( + 'Template File', + style: TextStyle( + fontSize: 15, + color: Color( + 0xFF00999E), // Add underline decoration + ), + ), + ), + ) + ])) + ], + ), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: Column( + children: [ + _buildFileUploadedGrid(), + const SizedBox(height: 16), + _buildPagination(context), + ], + ), + ) + + ], + ), + ], + ), + ); + } + + Widget _buildFileUploadedGrid() { + if (filteredData.isEmpty) { + return const SizedBox( + height: 120, + child: Center(child: Text('No uploaded files')), + ); + } + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 👈 2 cards per row + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 10, // 👈 card height + ), + itemCount: _paginatedData.length, + itemBuilder: (context, index) { + final item = _paginatedData[index]; + return _buildFileCard(item); + }, + ); + } + + Widget _buildFileCard(Map item) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + decoration: BoxDecoration( + color: const Color(0xFFEFF9FA), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFF9AD6DB)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + /// 📄 File Icon + Container( + height: 44, + width: 44, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF00A6A6)), + ), + child: const Icon( + Icons.description_outlined, + color: Color(0xFF00A6A6), + size: 22, + ), + ), + + const SizedBox(width: 12), + + /// 📑 LEFT CONTENT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Row 1 → File name + Text( + item['file_name'] ?? '-', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600, + color: const Color(0xFF101010), + ), + ), + + const SizedBox(height: 4), + + /// Row 2 → Action - Date + RichText( + text: TextSpan( + style: GoogleFonts.poppins(fontSize: 11), + children: [ + TextSpan( + text: item['file_action'] ?? '', + style: const TextStyle( + color: Color(0xFF00999E), + fontWeight: FontWeight.w500, + ), + ), + const TextSpan( + text: ' - ', + style: TextStyle(color: Color(0xFF585858)), + ), + TextSpan( + text: formatDate(item['created_at']), + style: const TextStyle(color: Color(0xFF585858)), + ), + ], + ), + ), + ], + ), + ), + + /// 👉 RIGHT SIDE (ICON + STATUS + DOWNLOAD) + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + /// 🔴 Error + Status + Row( + children: [ + + ], + ), + + const SizedBox(height: 8), + + Row( + children: [ + if (item['file_error_status'] == '1') + InkWell( + onTap: () async { + print(item); + // return; + final String? token = await tokenService.getCurrentToken(); + final String? enrollmentClient_id = await tokenService.readValue('enrollmentClient_id'); + final String? enrollmentEmpClientBranchId = await tokenService.readValue('enrollmentEmpClientBranchId'); + + print(item); + print(enrollmentClient_id); + print(widget.policyTypeId); + print(enrollmentEmpClientBranchId); + print(token); + print('post'); + print(widget.cardType); + print(widget.cardPolicyNo); + print(widget.cardInsurer_name); + print(widget.cardPolicy_name); + print(widget.cardPolicy_ExpDate); + print(item['id']); + + // ✅ SAFETY CHECK + if (token == null || + enrollmentClient_id == null || + enrollmentEmpClientBranchId == null) { + debugPrint('❌ Missing required data for navigation ${token}'); + return; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + excelErrorScreen( + ClientId: enrollmentClient_id, + policy_no: item['policy_no'], + action: item['file_action'], + created_at: item['created_at'], + clientBranchId: enrollmentEmpClientBranchId, + Token: token, + TokenType: 'pre', + id: item['id'] + ), + ), + ); + }, + child: Icon( + Icons.error, + size: 16, + color: Colors.red, + ), + ), + SizedBox(width: 10), + _buildStatusChip(item['status']), + SizedBox(width: 10), + InkWell( + onTap: () { + getHrFileDownload(item['id'], item['file_name']); + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: Color(0xFFC5F2F4), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF76CED2)), + ), + child: Icon( + Icons.file_download_outlined, + color: Color(0xFF1D1B20), + size: 22, + ), + ), + ), + ], + ), + /// ⬇ Download + + ], + ), + ], + ), + ); + } + + Widget _buildStatusChip(String status) { + final s = status.toLowerCase(); + + Color bg; + + if (s == 'success') { + bg = const Color(0xFF94E9B8); + } else if (s == 'failed') { + bg = const Color(0xFFFDC2C2); + } else if (s.contains('progress')) { + bg = const Color(0xFFFFE8AC); + } else { + bg = const Color(0xFFFBBF24); + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + status, + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ); + } + + 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()), + ), + ); + } + + String formatDate(String? dateString) { + if (dateString == null || dateString.isEmpty) return '-'; + + try { + DateTime parsedDate = DateTime.parse(dateString); + return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); + } catch (e) { + return '-'; + } + } + +} + +Widget _dateField({ + required String label, + required TextEditingController controller, + required VoidCallback onTap, +}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + const SizedBox(height: 6), + SizedBox( + height: 38, + child: TextField( + controller: controller, + readOnly: true, + onTap: onTap, + style: GoogleFonts.poppins(fontSize: 13), + decoration: InputDecoration( + hintText: 'Select date', + hintStyle: const TextStyle(color: Color(0xFF9E9E9E)), + suffixIcon: const Icon( + Icons.calendar_month_outlined, + size: 18, + color: Color(0xFF00999E), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide( + color: Color(0xFF00999E), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide( + color: Color(0xFF00999E), + width: 1.5, + ), + ), + ), + ), + ), + ], + ); +} + + +class Data { + final dynamic value; + final int row; + final int column; + final String sheet; + + Data(this.value, this.row, this.column, this.sheet); +} + +class _DependenceDataSource0 extends DataTableSource { + final List> _data; + _DependenceDataSource0(this._data); + + @override + DataRow getRow(int index) { + final row = _data[index]; + + String dob = row['DOB'] != null ? formatDate(row['DOB']) : 'N/A'; + String doj = row['DOJ'] != null ? formatDate(row['DOJ']) : 'N/A'; + + return DataRow(cells: [ + DataCell(Text(row['Sno'].toString())), + DataCell(Text(row['Emp_Code'].toString())), + DataCell(Text(row['Name'].toString())), + DataCell(Text(doj)), + DataCell(Text(row['Gender']?.toString() ?? 'N/A')), + DataCell(Text(row['Relation']?.toString() ?? 'N/A')), + DataCell(Text(dob)), + DataCell(Text(row['Mail']?.toString() ?? 'N/A')), + DataCell(Text(row['Mobile']?.toString() ?? 'N/A')), + DataCell(Text(row['SI']?.toString() ?? 'N/A')), + DataCell(Text(row['Grade']?.toString() ?? 'N/A')), + ]); + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount => _data.length; + + @override + int get selectedRowCount => 0; + + String formatDate(dynamic dateValue) { + if (dateValue is String) { + // If the date is already in string format + DateTime dateTime = DateTime.parse(dateValue); + return DateFormat('dd-MM-yyyy').format(dateTime); + } else if (dateValue is DateCellValue) { + // If dateValue is an instance of DateCellValue + return DateFormat('dd-MM-yyyy') + .format(DateTime.parse(dateValue.toString())); + } else { + // Handle other cases or null values + return 'N/A'; + } + } +} diff --git a/lib/service/api_service.dart b/lib/service/api_service.dart index b7571c0..0d4f43f 100755 --- a/lib/service/api_service.dart +++ b/lib/service/api_service.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'dart:convert'; import '../config/environment.dart'; @@ -8,6 +8,7 @@ import '../customAppBar/toastHelper.dart'; class ApiService { final BuildContext context; + final tokenService = TokenStorageService(); String? _token; String? _hrtoken; bool _isSessionOutToastShown = false; // Flag to track toast message @@ -17,10 +18,7 @@ class ApiService { } Future _initializeToken() async { - final prefs = await SharedPreferences.getInstance(); - _token = prefs.getString('token') ?? ''; - // final hrprefs = await SharedPreferences.getInstance(); - // _hrtoken = hrprefs.getString('hrtoken') ?? ''; + _token = tokenService.getCurrentToken() ?? ''; } Future getTokenLoadAPI(token) async { @@ -33,9 +31,9 @@ class ApiService { if (_token == null) { await _initializeToken(); } - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final empClientId = prefs.getString('empClientId'); - final empClientBranchId = prefs.getString('empClientBranchId'); + final empClientId = await tokenService.readValue('empClientId'); + final empClientBranchId = await tokenService.readValue('empClientBranchId'); + final url = Uri.parse( '${Environment.apiUrl}getClientDetails?post_client_id=$empClientId&post_branch_id=$empClientBranchId&pre_client_id=$clientId&pre_branch_id=$branchID'); final headers = { @@ -45,6 +43,24 @@ class ApiService { return response; } + Future> getActiveAndInactivePolicyDetails( + String? clientId, + String? empCode, + String? status, + String? branchID, + String? mobileNo,String? emailId) async { + if (_token == null) { + await _initializeToken(); + } + final url = Uri.parse( + '${Environment.apiUrl}getEmployeeActiveOrInactivePolicy?client_id=${clientId ?? ''}&emp_code=${empCode ?? ''}&type=${status ?? ''}&client_branch_id=${branchID ?? ''}&mobile_no=${mobileNo ?? ''}&email_id=${emailId ?? ''}'); + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + }; + final response = await _makeGetRequest(url, headers); + return response; + } + Future> getSelfEmployeeProfileToApi( token, String clientId, @@ -163,6 +179,26 @@ class ApiService { return response; } + Future> getExcelFileErrorsApi(id,type) async { + print(_token); + if (_token == null) { + await _initializeToken(); + } + final apiURL; + if(type == 'post'){ + apiURL = Environment.apiUrlPost; + } else { + apiURL = Environment.apiUrl; + } + final url = Uri.parse( + '${apiURL}getExcelFileErrors/${id}/api'); + final headers = { + 'Authorization': 'Bearer $_token' ?? '', + }; + final response = await _makeGetRequest(url, headers); + return response; + } + Future> removeAddonsGmcDependentToAPI( String empCodeString, String addOnsDependentClientPolicyId) async { print(_token); @@ -478,7 +514,8 @@ class ApiService { final url = Uri.parse('${Environment.apiUrlPost}logHrActivity'); final headers = { - 'Authorization': 'Bearer $token' ?? '', + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }; final body = { @@ -503,6 +540,39 @@ class ApiService { } } + Future> getEcardBulkDownloadApi(client_policy_id,empHrId,emp_policy_ids,String token) async { + + // final url = Uri.parse( + // '${Environment.apiUrlPost}logHrActivity?user_id=$postId&pre_hr_id=$preId&user_type=hr&activity=$activity'); + + final url = Uri.parse('${Environment.apiUrlPost}bulkEcardDownloadAsZip'); + + final headers = { + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + }; + + final body = { + 'client_policy_id': client_policy_id, + 'hr_id': empHrId, + 'emp_policy_ids':emp_policy_ids, + }; + + final response = await http.post( + url, + headers: headers, + body: jsonEncode(body), + ); + + // final response = await _makeGetRequest(url, headers); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception( + 'Failed to log HR activity: ${response.statusCode} ${response.body}'); + } + } + Future> getPostLogHrActivity( String? postId, String? preId, String token, String activity) async { print("getCashDepositDetailsToApi1"); @@ -516,9 +586,10 @@ class ApiService { final url = Uri.parse('${Environment.apiUrlPost}logHrActivity'); - final headers = { - 'Authorization': 'Bearer $token' ?? '', - }; + final headers = { + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + }; final body = { 'user_id': postId, @@ -553,7 +624,8 @@ class ApiService { final url = Uri.parse('${Environment.apiUrl}logHrActivity'); final headers = { - 'Authorization': 'Bearer $token' ?? '', + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }; final body = { @@ -578,6 +650,32 @@ class ApiService { } } + Future> postHrDashboard(params,token) async { + + final url = Uri.parse('${Environment.apiUrlPost}getHrDashboad'); + + final headers = { + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', + }; + + final body = params; + + final response = await http.post( + url, + headers: headers, + body: jsonEncode(body), + ); + + // final response = await _makeGetRequest(url, headers); + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception( + 'Failed to log HR activity: ${response.statusCode} ${response.body}'); + } + } + Future> getClaimPoliciesToApi(String token) async { print("getgetClaimPoliciesToApii1"); final url = Uri.parse('${Environment.apiUrlPost}claimsSearch'); @@ -589,6 +687,16 @@ class ApiService { return response; } + Future> getClaimPoliciesFileDownload(id,String token) async { + final url = Uri.parse('${Environment.apiUrlPost}hrFileDownload?id=$id'); + + final headers = { + 'Authorization': 'Bearer $token' ?? '', + }; + final response = await _makeGetRequest(url, headers); + return response; + } + Future> getClaimPoliciesListDataToApi( String token, Map body) async { print("getgetClaimPoliciesToApii1"); @@ -638,6 +746,30 @@ class ApiService { return response; } + Future> getOpenEndorsementFileData(id, String token) async { + print("getPolicyAndEndorsementFiles"); + final url = Uri.parse( + '${Environment.apiUrlPost}downloadPolicyFiles?file_id=$id'); + + final headers = { + 'Authorization': 'Bearer $token' ?? '', + }; + final response = await _makeGetRequest(url, headers); + return response; + } + + Future> getCdEndorsementData(id, String token) async { + print("getPolicyAndEndorsementFiles"); + final url = Uri.parse( + '${Environment.apiUrlPost}getPolicyAndEndorsementFiles?cd_ac_pk=$id'); + + final headers = { + 'Authorization': 'Bearer $token' ?? '', + }; + final response = await _makeGetRequest(url, headers); + return response; + } + Future> getClaimsHistoryToApi( String ticket_type_id, String token) async { print("getCashDepositDetailsToApi1"); @@ -663,7 +795,7 @@ class ApiService { } Future> getEmployeeAndDependenceToApi( - String clintID, String getPolicyNo, String empRefId, String token) async { + String clintID, getPolicyNo, String empRefId, String token) async { print(_hrtoken); if (token == null) { await _initializeToken(); @@ -691,15 +823,22 @@ class ApiService { return response; } - Future> getFileListToApi(empPrimaryId,cardPolicyNo,String token) async { + Future> getFileListToApi(empPrimaryId,cardPolicyNo,empClientId,String token,String type) async { print(_hrtoken); if (token == null) { await _initializeToken(); + } + final apiURL; + if(type == 'post'){ + apiURL = Environment.apiUrlPost; + } else { + apiURL = Environment.apiUrl; } final url = Uri.parse( - '${Environment.apiUrlPost}hrFileList?created_by=$empPrimaryId&policy_no=$cardPolicyNo'); + '${apiURL}hrFileList?created_by=$empPrimaryId&policy_no=$cardPolicyNo&client_id=$empClientId'); final headers = { - 'Authorization': 'Bearer $token' ?? '', + 'Authorization': 'Bearer ${token ?? ''}', + 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }; final response = await _makeGetRequest(url, headers); return response; @@ -815,8 +954,10 @@ class ApiService { // Clear only mobile storage // if (!isWeb) { - final prefs = await SharedPreferences.getInstance(); - await prefs.clear(); + await tokenService.clearAll(); + + print('Secure Storage Cleared'); + // } if (context.mounted) { print('context.mounted'); @@ -860,16 +1001,26 @@ class ApiService { await _clearLocalStorageAndRedirect(); } return {}; + } else if (response.statusCode == 429) { + final body = jsonDecode(response.body); + final message = body['message']; + ToastHelper.showWarningToast(context, message); + return {}; } else { throw Exception('Failed to load data'); } } Future _clearLocalStorageAndRedirect() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.clear(); - // Assuming you have access to the context + await tokenService.clearAll(); // 🔐 clears flutter_secure_storage + ToastHelper.showErrorToast(context, 'Session Out'); - Navigator.pushNamed(context, 'hrLogin'); + if (!context.mounted) return; + Navigator.pushNamedAndRemoveUntil( + context, + 'hrLogin', + (route) => false, + ); } + } diff --git a/lib/service/file_upload_service.dart b/lib/service/file_upload_service.dart new file mode 100755 index 0000000..643d002 --- /dev/null +++ b/lib/service/file_upload_service.dart @@ -0,0 +1,70 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/widgets.dart'; + +class UploadedFile { + final PlatformFile file; + final TextEditingController controller; + UploadedFile({required this.file}) + : controller = TextEditingController( + text: file.extension != null + ? file.name.replaceAll('.${file.extension}', '') + : file.name); + + String get label => controller.text; + void dispose() => controller.dispose(); +} + +class FileUploadService { + FileUploadService._(); + static final FileUploadService _instance = FileUploadService._(); + factory FileUploadService() => _instance; + + final List _files = []; + + // Expose as unmodifiable but containing UploadedFile objects + List get files => List.unmodifiable(_files); + + // Allowed extensions + static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg', 'heic']; + + Future pickFiles({int maxFileSizeInMB = 3}) async { + final result = await FilePicker.platform.pickFiles( + allowMultiple: true, + withData: true, + type: FileType.custom, + allowedExtensions: allowedExtensions, + ); + + if (result != null) { + for (final pf in result.files) { + final ext = pf.extension?.toLowerCase() ?? ''; + final sizeInMB = (pf.size / (1024 * 1024)); + + if (!allowedExtensions.contains(ext)) { + return "Unsupported file format: ${pf.name}"; + } + if (sizeInMB > maxFileSizeInMB) { + return "File too large (${pf.name}). Max $maxFileSizeInMB MB allowed."; + } + + // Wrap PlatformFile in our UploadedFile model + _files.add(UploadedFile(file: pf)); + } + } + return null; + } + + void removeFileAt(int index) { + if (index >= 0 && index < _files.length) { + _files[index].dispose(); + _files.removeAt(index); + } + } + + void clearAll() { + for (final f in _files) { + f.dispose(); + } + _files.clear(); + } +} diff --git a/lib/service/hrDashboardTabs/activePolicies.dart b/lib/service/hrDashboardTabs/activePolicies.dart index 465ef33..133be4e 100755 --- a/lib/service/hrDashboardTabs/activePolicies.dart +++ b/lib/service/hrDashboardTabs/activePolicies.dart @@ -1,462 +1,462 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/cupertino.dart'; -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 '../../hrPolicyDetails.dart'; -import '../api_service.dart'; - -class ActivePolicies extends StatefulWidget { - final String empClientId; - final String empClientBranchId; - final String empHrId; - final String postToken; - - const ActivePolicies( - {Key? key, - required this.empClientId, - required this.empClientBranchId, - required this.postToken, - required this.empHrId}); - - @override - State createState() => _ActivePolicieState(); -} - -class _ActivePolicieState extends State { - late ApiService apiService; - dynamic getCardArrays = []; - int selectedIndex = 1; - int stausVal = 1; - bool isLoading = false; - - // List> getCardArrays = [ - // { - // "client_policy_id": 392, - // "client_id": 58, - // "policy_type_id": 2, - // "is_addon": 1, - // "OpenForEnrollment": 1, - // "inception_type": 2, - // "policy_no": "GMC-MA8596745566998855885", - // "insurer_id": 1, - // "type": "GMC", - // "policy_name": "Group Medical Coverage", - // "insurer_name": "Life Insurance Corporation of India (LIC)", - // "insurer_short_name": "LIC", - // "totalMembersCount": 7, - // "membersCountOfEnrolled": 0, - // "membersCountOfDraft": 7 - // }, - // { - // "client_policy_id": 392, - // "client_id": 58, - // "policy_type_id": 2, - // "is_addon": 1, - // "OpenForEnrollment": 1, - // "inception_type": 2, - // "policy_no": "GMC-MA8596745566998855885", - // "insurer_id": 1, - // "type": "GMC", - // "policy_name": "Group Medical Coverage", - // "insurer_name": "Life Insurance Corporation of India (LIC)", - // "insurer_short_name": "LIC", - // "totalMembersCount": 7, - // "membersCountOfEnrolled": 0, - // "membersCountOfDraft": 7 - // }, - // - // ]; - - @override - void initState() { - super.initState(); - apiService = ApiService(context); - _loadData(); - - print("_PreEnrollmentState 1"); - } - - Future _loadData() async { - await getCashDepositDetails(widget.empClientBranchId, widget.empClientId, - widget.empHrId, widget.postToken); - } - - Future getCashDepositDetails( - clintBranchId, clintID, hr_id, token) async { - print("_PreEnrollmentState 2"); - print('IN'); - print("clintBranchId -$clintBranchId"); - print("clintID -$clintID"); - print("hr_id -$hr_id"); - print("token -$token"); - - isLoading = true; - // setState(() { - // _isLoading = true; - // }); - try { - if (clintBranchId == null || clintID == null) { - return; - } - - final response = await apiService.getActiveCashDepositDetailsToApi( - clintID!, clintBranchId!, hr_id, token, stausVal); - - // final response = await apiService.getCashDepositDetailsToApi( - // clintID!, clintBranchId!, hr_id, token); - print('IN1'); - if (response['status'] == 'success') { - isLoading = false; - setState(() { - print('response'); - print(response['data']); - - print("_PreEnrollmentState 3"); - setState(() { - getCardArrays = List>.from(response['data']); - }); - - print('getCardArrays'); - print(getCardArrays); - }); - - print('IN2'); - print("getCardArrays9 - $getCardArrays"); - } else { - isLoading = false; - print('API request failed with status'); - setState(() { - getCardArrays = []; - }); - } - } catch (e) { - print('Exception occurred: $e'); - } - } - - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - final screenHeight = MediaQuery.of(context).size.height; - - final crossAxisCount = 4; - final spacing = 20.0; // crossAxisSpacing - final totalSpacing = (crossAxisCount - 1) * spacing; - final itemWidth = (screenWidth - totalSpacing) / crossAxisCount; - -// Example: target card height - final itemHeight = screenHeight * 0.2; - -// Dynamic aspect ratio: - final aspectRatio = screenWidth / screenHeight; - - print("_PreEnrollmentState 4"); - // TODO: implement build - return Container( - // height: MediaQuery.of(context).size.height * 0.2, - - // height: 400, - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - // color: Colors.yellow.shade100, - borderRadius: BorderRadius.circular(16), - ), - // - - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - // padding: const EdgeInsets.only(left: 16.0, right: 16.0), - decoration: BoxDecoration( - color: Colors.grey.shade50, - boxShadow: const [ - BoxShadow( - color: Colors.black54, // Grey shadow - spreadRadius: 0.2, - blurRadius: 6, - offset: Offset(0, 1), // Horizontal, Vertical - ), - ], - borderRadius: BorderRadius.circular(16), - ), - - child: Row( - children: [ - buildTab("Active", 0), - const SizedBox(width: 10), - buildTab("Expired", 1), - ], - ), - ), - ], - ), - const SizedBox( - height: 15, - ), - isLoading - ? Expanded( - // 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 - ), - ) - : Flexible( - child: Container( - // color: Colors.redAccent.shade100, - // height: MediaQuery.of(context).size.height * 0.4, - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - child: Container( - // color: Colors.redAccent.shade100, - // color: Colors.white, - // color: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 16.0), - // height: MediaQuery.of(context).size.height * 0.4, - // height: 400, - - child: getCardArrays.isEmpty - ? Center( - child: Text( - 'No Policy Mapping Found', - style: TextStyle( - fontSize: 16, - color: Colors.grey.shade600, - ), - ), - ) - : GridView.builder( - itemCount: getCardArrays.length, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 20, - mainAxisSpacing: 20, - // childAspectRatio: 2, - childAspectRatio: aspectRatio, - // childAspectRatio: 2.1, - ), - itemBuilder: (context, index) { - return buildPolicyCard(getCardArrays[index]); - }, - ), - ), - // ], - ), - ), - ], - ), - ); - } - - Widget buildPolicyCard(Map policy) { - print("buildPolicyCard - $policy"); - final mediaQuery = MediaQuery.of(context); - final devicePixelRatio = mediaQuery.devicePixelRatio; - final logicalWidth = 230 / devicePixelRatio; - final logicalHeight = 115 / devicePixelRatio; - - print("logicalWidth - $logicalWidth"); - print("logicalHeight - $logicalHeight"); - return InkWell( - onTap: () { - setState(() { - print("policytab - $policy"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => hrPolicyDetails( - ClientId: policy['client_id'].toString(), // <-- from map - policyTypeId: - policy['policy_type_id'].toString(), // <-- from map - ClientPoliyId: policy['client_policy_id'].toString(), - clientBranchId: widget.empClientBranchId, - Token: widget.postToken, - TokenType: 'post', - cardType: policy['type'].toString(), - cardPolicyNo: policy['policy_no'].toString(), - cardInsurer_name: policy['insurer_short_name'].toString(), - cardPolicy_name: policy['policy_name'].toString(), - cardPolicy_ExpDate: policy['policy_expiry_date'].toString(), - ), - ), - ); - }); - }, - - // height: MediaQuery.of(context).size.height * 0.1, - // width: MediaQuery.of(context).size.height * 0.1, - // width: logicalWidth, - // height: logicalHeight, - child: Container( - margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), - // height: MediaQuery.of(context).size.height * 1, - // width: MediaQuery.of(context).size.height * 0.1, - // margin: EdgeInsets.only(bottom: 10.0), - decoration: BoxDecoration( - // color: Colors.yellow.shade50, - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - spreadRadius: 1, - offset: Offset(0, 0), // Equal shadow in all directions - ), - ], - ), - // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: MediaQuery.of(context).size.height * 0.07, - // color: Colors.green.shade100, - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${policy['type']} - ${policy['policy_no']} " ?? '', - style: const TextStyle( - fontFamily: "Inter", - fontWeight: FontWeight.w600, - fontSize: 12, - ), - ), - policy['insurer_name'] != '' - ? Text( - // "${policy['insurer_short_name']} - ${policy['policy_name']} " ?? - // '', - - policy['insurer_name'] ?? "", - style: const TextStyle( - fontSize: 11, - color: Colors.grey, - ), - ) - : SizedBox.shrink(), - ], - ), - ], - ), - ), - // Spacer(), - Container( - // color: Colors.pink.shade50, - height: MediaQuery.of(context).size.height * 0.09, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildCountBox( - policy['membersCountOfActive'].toString(), - "Active", - Color(0xFF7BD9B6), - ), - - // Spacer(), - _buildCountBox( - policy['membersCountOfInactive'].toString(), - "Inactive", - Color(0xFFFFA6A6), - ), - // Spacer(), - // _buildCountBox( - // policy['totalMembersCount'].toString(), "Total"), - // Spacer(), - ], - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildCountBox(String count, String label, Color boxColor) { - return Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - // width: 70, - // height: 40, - height: MediaQuery.of(context).size.height * 0.05, - width: MediaQuery.of(context).size.height * 0.1, - alignment: Alignment.center, - decoration: BoxDecoration( - color: boxColor, - // color: const Color(0xFFDFF1F3), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - count, - style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), - ), - ), - // Spacer(), - const SizedBox(height: 8), - Text(label, - style: const TextStyle(fontSize: 10, color: Color(0xFF848484))), - ], - ); - } - - Widget buildTab(String title, int index) { - final isSelected = selectedIndex == index + 1; - return InkWell( - onTap: () { - setState(() { - selectedIndex = index + 1; - if (selectedIndex == 2) { - stausVal = 0; - } else { - stausVal = 1; - } - _loadData(); - }); - }, - child: Container( - padding: const EdgeInsets.only( - top: 6.5, bottom: 6.0, left: 20.0, right: 20.0), - decoration: BoxDecoration( - color: isSelected ? const Color(0xFF009195) : Colors.transparent, - boxShadow: isSelected - ? [ - BoxShadow( - color: isSelected - ? const Color(0xFF009195) - : Colors.transparent, // Grey shadow - spreadRadius: 0.2, - blurRadius: 1, - offset: const Offset(0, 1), // Horizontal, Vertical - ), - ] - : [], - borderRadius: BorderRadius.circular(16), - ), - child: Text(title, - style: GoogleFonts.poppins( - fontSize: 11, - color: isSelected ? Colors.white : Colors.black, - fontWeight: FontWeight.w500, - ))), - ); - } -} +// import 'package:firebase_auth/firebase_auth.dart'; +// import 'package:flutter/cupertino.dart'; +// 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 '../../presentation/hrPolicyDetails.dart'; +// import '../api_service.dart'; +// +// class ActivePolicies extends StatefulWidget { +// final String empClientId; +// final String empClientBranchId; +// final String empHrId; +// final String postToken; +// +// const ActivePolicies( +// {Key? key, +// required this.empClientId, +// required this.empClientBranchId, +// required this.postToken, +// required this.empHrId}); +// +// @override +// State createState() => _ActivePolicieState(); +// } +// +// class _ActivePolicieState extends State { +// late ApiService apiService; +// dynamic getCardArrays = []; +// int selectedIndex = 1; +// int stausVal = 1; +// bool isLoading = false; +// +// // List> getCardArrays = [ +// // { +// // "client_policy_id": 392, +// // "client_id": 58, +// // "policy_type_id": 2, +// // "is_addon": 1, +// // "OpenForEnrollment": 1, +// // "inception_type": 2, +// // "policy_no": "GMC-MA8596745566998855885", +// // "insurer_id": 1, +// // "type": "GMC", +// // "policy_name": "Group Medical Coverage", +// // "insurer_name": "Life Insurance Corporation of India (LIC)", +// // "insurer_short_name": "LIC", +// // "totalMembersCount": 7, +// // "membersCountOfEnrolled": 0, +// // "membersCountOfDraft": 7 +// // }, +// // { +// // "client_policy_id": 392, +// // "client_id": 58, +// // "policy_type_id": 2, +// // "is_addon": 1, +// // "OpenForEnrollment": 1, +// // "inception_type": 2, +// // "policy_no": "GMC-MA8596745566998855885", +// // "insurer_id": 1, +// // "type": "GMC", +// // "policy_name": "Group Medical Coverage", +// // "insurer_name": "Life Insurance Corporation of India (LIC)", +// // "insurer_short_name": "LIC", +// // "totalMembersCount": 7, +// // "membersCountOfEnrolled": 0, +// // "membersCountOfDraft": 7 +// // }, +// // +// // ]; +// +// @override +// void initState() { +// super.initState(); +// apiService = ApiService(context); +// _loadData(); +// +// print("_PreEnrollmentState 1"); +// } +// +// Future _loadData() async { +// await getCashDepositDetails(widget.empClientBranchId, widget.empClientId, +// widget.empHrId, widget.postToken); +// } +// +// Future getCashDepositDetails( +// clintBranchId, clintID, hr_id, token) async { +// print("_PreEnrollmentState 2"); +// print('IN'); +// print("clintBranchId -$clintBranchId"); +// print("clintID -$clintID"); +// print("hr_id -$hr_id"); +// print("token -$token"); +// +// isLoading = true; +// // setState(() { +// // _isLoading = true; +// // }); +// try { +// if (clintBranchId == null || clintID == null) { +// return; +// } +// +// final response = await apiService.getActiveCashDepositDetailsToApi( +// clintID!, clintBranchId!, hr_id, token, stausVal); +// +// // final response = await apiService.getCashDepositDetailsToApi( +// // clintID!, clintBranchId!, hr_id, token); +// print('IN1'); +// if (response['status'] == 'success') { +// isLoading = false; +// setState(() { +// print('response'); +// print(response['data']); +// +// print("_PreEnrollmentState 3"); +// setState(() { +// getCardArrays = List>.from(response['data']); +// }); +// +// print('getCardArrays'); +// print(getCardArrays); +// }); +// +// print('IN2'); +// print("getCardArrays9 - $getCardArrays"); +// } else { +// isLoading = false; +// print('API request failed with status'); +// setState(() { +// getCardArrays = []; +// }); +// } +// } catch (e) { +// print('Exception occurred: $e'); +// } +// } +// +// @override +// Widget build(BuildContext context) { +// final screenWidth = MediaQuery.of(context).size.width; +// final screenHeight = MediaQuery.of(context).size.height; +// +// final crossAxisCount = 4; +// final spacing = 20.0; // crossAxisSpacing +// final totalSpacing = (crossAxisCount - 1) * spacing; +// final itemWidth = (screenWidth - totalSpacing) / crossAxisCount; +// +// // Example: target card height +// final itemHeight = screenHeight * 0.2; +// +// // Dynamic aspect ratio: +// final aspectRatio = screenWidth / screenHeight; +// +// print("_PreEnrollmentState 4"); +// // TODO: implement build +// return Container( +// // height: MediaQuery.of(context).size.height * 0.2, +// +// // height: 400, +// padding: const EdgeInsets.all(16.0), +// decoration: BoxDecoration( +// color: Colors.white, +// // color: Colors.yellow.shade100, +// borderRadius: BorderRadius.circular(16), +// ), +// // +// +// child: Column( +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.end, +// children: [ +// Container( +// // padding: const EdgeInsets.only(left: 16.0, right: 16.0), +// decoration: BoxDecoration( +// color: Colors.grey.shade50, +// boxShadow: const [ +// BoxShadow( +// color: Colors.black54, // Grey shadow +// spreadRadius: 0.2, +// blurRadius: 6, +// offset: Offset(0, 1), // Horizontal, Vertical +// ), +// ], +// borderRadius: BorderRadius.circular(16), +// ), +// +// child: Row( +// children: [ +// buildTab("Active", 0), +// const SizedBox(width: 10), +// buildTab("Expired", 1), +// ], +// ), +// ), +// ], +// ), +// const SizedBox( +// height: 15, +// ), +// isLoading +// ? Expanded( +// // 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 +// ), +// ) +// : Flexible( +// child: Container( +// // color: Colors.redAccent.shade100, +// // height: MediaQuery.of(context).size.height * 0.4, +// // mainAxisAlignment: MainAxisAlignment.center, +// // children: [ +// child: Container( +// // color: Colors.redAccent.shade100, +// // color: Colors.white, +// // color: Colors.white, +// padding: EdgeInsets.symmetric(horizontal: 16.0), +// // height: MediaQuery.of(context).size.height * 0.4, +// // height: 400, +// +// child: getCardArrays.isEmpty +// ? Center( +// child: Text( +// 'No Policy Mapping Found', +// style: TextStyle( +// fontSize: 16, +// color: Colors.grey.shade600, +// ), +// ), +// ) +// : GridView.builder( +// itemCount: getCardArrays.length, +// gridDelegate: +// SliverGridDelegateWithFixedCrossAxisCount( +// crossAxisCount: 4, +// crossAxisSpacing: 20, +// mainAxisSpacing: 20, +// // childAspectRatio: 2, +// childAspectRatio: aspectRatio, +// // childAspectRatio: 2.1, +// ), +// itemBuilder: (context, index) { +// return buildPolicyCard(getCardArrays[index]); +// }, +// ), +// ), +// // ], +// ), +// ), +// ], +// ), +// ); +// } +// +// Widget buildPolicyCard(Map policy) { +// print("buildPolicyCard - $policy"); +// final mediaQuery = MediaQuery.of(context); +// final devicePixelRatio = mediaQuery.devicePixelRatio; +// final logicalWidth = 230 / devicePixelRatio; +// final logicalHeight = 115 / devicePixelRatio; +// +// print("logicalWidth - $logicalWidth"); +// print("logicalHeight - $logicalHeight"); +// return InkWell( +// onTap: () { +// setState(() { +// print("policytab - $policy"); +// Navigator.push( +// context, +// MaterialPageRoute( +// builder: (context) => hrPolicyDetails( +// ClientId: widget.empClientId, // <-- from map +// policyTypeId: +// policy['policy_type_id'].toString(), // <-- from map +// ClientPoliyId: policy['client_policy_id'].toString(), +// clientBranchId: widget.empClientBranchId, +// Token: widget.postToken, +// TokenType: 'post', +// cardType: policy['type'].toString(), +// cardPolicyNo: policy['policy_no'].toString(), +// cardInsurer_name: policy['insurer_short_name'].toString(), +// cardPolicy_name: policy['policy_name'].toString(), +// cardPolicy_ExpDate: policy['policy_expiry_date'].toString(), +// ), +// ), +// ); +// }); +// }, +// +// // height: MediaQuery.of(context).size.height * 0.1, +// // width: MediaQuery.of(context).size.height * 0.1, +// // width: logicalWidth, +// // height: logicalHeight, +// child: Container( +// margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), +// // height: MediaQuery.of(context).size.height * 1, +// // width: MediaQuery.of(context).size.height * 0.1, +// // margin: EdgeInsets.only(bottom: 10.0), +// decoration: BoxDecoration( +// // color: Colors.yellow.shade50, +// color: Colors.white, +// borderRadius: BorderRadius.circular(12), +// boxShadow: const [ +// BoxShadow( +// color: Colors.black12, +// blurRadius: 6, +// spreadRadius: 1, +// offset: Offset(0, 0), // Equal shadow in all directions +// ), +// ], +// ), +// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), +// child: Padding( +// padding: const EdgeInsets.all(12), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Container( +// height: MediaQuery.of(context).size.height * 0.07, +// // color: Colors.green.shade100, +// child: Row( +// children: [ +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text( +// "${policy['type']} - ${policy['policy_no']} " ?? '', +// style: const TextStyle( +// fontFamily: "Inter", +// fontWeight: FontWeight.w600, +// fontSize: 12, +// ), +// ), +// policy['insurer_name'] != '' +// ? Text( +// // "${policy['insurer_short_name']} - ${policy['policy_name']} " ?? +// // '', +// +// policy['insurer_name'] ?? "", +// style: const TextStyle( +// fontSize: 11, +// color: Colors.grey, +// ), +// ) +// : SizedBox.shrink(), +// ], +// ), +// ], +// ), +// ), +// // Spacer(), +// Container( +// // color: Colors.pink.shade50, +// height: MediaQuery.of(context).size.height * 0.09, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceEvenly, +// children: [ +// _buildCountBox( +// policy['membersCountOfActive'].toString(), +// "Active", +// Color(0xFF7BD9B6), +// ), +// +// // Spacer(), +// _buildCountBox( +// policy['membersCountOfInactive'].toString(), +// "Inactive", +// Color(0xFFFFA6A6), +// ), +// // Spacer(), +// // _buildCountBox( +// // policy['totalMembersCount'].toString(), "Total"), +// // Spacer(), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// ); +// } +// +// Widget _buildCountBox(String count, String label, Color boxColor) { +// return Column( +// // mainAxisAlignment: MainAxisAlignment.spaceBetween, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// // width: 70, +// // height: 40, +// height: MediaQuery.of(context).size.height * 0.05, +// width: MediaQuery.of(context).size.height * 0.1, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: boxColor, +// // color: const Color(0xFFDFF1F3), +// borderRadius: BorderRadius.circular(8), +// ), +// child: Text( +// count, +// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), +// ), +// ), +// // Spacer(), +// const SizedBox(height: 8), +// Text(label, +// style: const TextStyle(fontSize: 10, color: Color(0xFF848484))), +// ], +// ); +// } +// +// Widget buildTab(String title, int index) { +// final isSelected = selectedIndex == index + 1; +// return InkWell( +// onTap: () { +// setState(() { +// selectedIndex = index + 1; +// if (selectedIndex == 2) { +// stausVal = 0; +// } else { +// stausVal = 1; +// } +// _loadData(); +// }); +// }, +// child: Container( +// padding: const EdgeInsets.only( +// top: 6.5, bottom: 6.0, left: 20.0, right: 20.0), +// decoration: BoxDecoration( +// color: isSelected ? const Color(0xFF009195) : Colors.transparent, +// boxShadow: isSelected +// ? [ +// BoxShadow( +// color: isSelected +// ? const Color(0xFF009195) +// : Colors.transparent, // Grey shadow +// spreadRadius: 0.2, +// blurRadius: 1, +// offset: const Offset(0, 1), // Horizontal, Vertical +// ), +// ] +// : [], +// borderRadius: BorderRadius.circular(16), +// ), +// child: Text(title, +// style: GoogleFonts.poppins( +// fontSize: 11, +// color: isSelected ? Colors.white : Colors.black, +// fontWeight: FontWeight.w500, +// ))), +// ); +// } +// } diff --git a/lib/service/hrDashboardTabs/cd.dart b/lib/service/hrDashboardTabs/cd.dart index c159a1d..0f11fca 100755 --- a/lib/service/hrDashboardTabs/cd.dart +++ b/lib/service/hrDashboardTabs/cd.dart @@ -7,13 +7,14 @@ 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:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; -import '../../cdTransactionDetails.dart'; +import '../../presentation/cdTransactionDetails.dart'; import '../api_service.dart'; import 'package:collection/collection.dart'; +import '../token_storage_service.dart'; + class CdPolicies extends StatefulWidget { final String empClientId; final String empClientBranchId; @@ -31,6 +32,7 @@ class CdPolicies extends StatefulWidget { } class _CdPolicieState extends State { + final tokenService = TokenStorageService(); Uint8List? fileBytes; List> getCDPolicies = []; bool isLoading = false; @@ -188,9 +190,8 @@ class _CdPolicieState extends State { Future handleExportAction() async { print('handleExportAction'); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final postId = prefs.getString('empHrId'); - final preId = prefs.getString('enrollmentEmpPrimaryId'); + final postId = await tokenService.readValue('empHrId'); + final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); var activity = "export_cddata"; print('postId - $postId'); @@ -502,8 +503,7 @@ class _CdPolicieState extends State { cdMasterAccountNo: item['cd_master_account_no'], insurerId: item['insurer_id'], cd_ac_pk: item['cd_ac_pk'], - empClientId: widget.empClientId, - postToken: widget.postToken), + empClientId: widget.empClientId), ), ); }, diff --git a/lib/service/hrDashboardTabs/preEnrollment.dart b/lib/service/hrDashboardTabs/preEnrollment.dart index 412677c..ae3e18a 100755 --- a/lib/service/hrDashboardTabs/preEnrollment.dart +++ b/lib/service/hrDashboardTabs/preEnrollment.dart @@ -1,374 +1,374 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/cupertino.dart'; -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 '../../hrPolicyDetails.dart'; -import '../api_service.dart'; - -class PreEnrollment extends StatefulWidget { - final String enrollmentClientId; - final String enrollmentClientBranchId; - final String enrollmentHrId; - final String enrollToken; - - const PreEnrollment( - {Key? key, - required this.enrollmentClientId, - required this.enrollmentClientBranchId, - required this.enrollmentHrId, - required this.enrollToken}); - - @override - State createState() => _PreEnrollmentState(); -} - -class _PreEnrollmentState extends State { - late ApiService apiService; - dynamic getCardArrays = []; - int selectedIndex = 0; - bool isLoading = false; - // List> getCardArrays = [ - // { - // "client_policy_id": "415", - // "client_id": "187", - // "policy_type_id": "2", - // "is_addon": "1", - // "OpenForEnrollment": "0", - // "inception_type": "1", - // "policy_no": "GMC-2002/2022/2005", - // "insurer_id": null, - // "policy_expiry_date": "06-07-2026", - // "type": "GMC", - // "policy_name": "Group Medical Coverage", - // "insurer_name": null, - // "insurer_short_name": null, - // "totalMembersCount": 17, - // "membersCountOfEnrolled": 0, - // "membersCountOfDraft": 17 - // }, - // { - // "client_policy_id": 396, - // "client_id": 58, - // "policy_type_id": 4, - // "is_addon": 2, - // "OpenForEnrollment": 1, - // "inception_type": 2, - // "policy_no": "GMC-MA8596745566998855885", - // "insurer_id": 1, - // "type": "GMC - Topup", - // "policy_name": "Group Medical Coverage Topup", - // "insurer_name": "Life Insurance Corporation of India (LIC)", - // "insurer_short_name": "LIC", - // "totalMembersCount": 7, - // "membersCountOfEnrolled": 0, - // "membersCountOfDraft": 7 - // }, - // - // ]; - - @override - void initState() { - super.initState(); - apiService = ApiService(context); - _loadData(); - - print("_PreEnrollmentState 1"); - } - - Future _loadData() async { - await getCashDepositDetails(widget.enrollmentClientBranchId, - widget.enrollmentClientId, widget.enrollmentHrId, widget.enrollToken); - } - - Future getCashDepositDetails( - clintBranchId, clintID, hr_id, token) async { - print("_PreEnrollmentState 2"); - print('IN'); - print("clintBranchId -$clintBranchId"); - print("clintID -$clintID"); - print("hr_id -$hr_id"); - print("token -$token"); - - // isLoading = true; - setState(() { - isLoading = true; - }); - try { - if (clintBranchId == null || clintID == null) { - return; - } - final response = await apiService.getCashDepositDetailsToApi( - clintID!, clintBranchId!, hr_id, token); - - // final response = await apiService.getCashDepositDetailsToApi( - // clintID!, clintBranchId!, hr_id, token); - print('IN1'); - if (response['status'] == 'success') { - - setState(() { - isLoading = false; - print('response'); - print(response['data']); - - print("_PreEnrollmentState 3"); - // getCardArrays = []; - getCardArrays = List>.from(response['data']); - print('getCardArrays'); - print(getCardArrays); - }); - - print('IN2'); - print("getCardArrays9 - $getCardArrays"); - } else { - setState(() { - isLoading = false; - }); - - print('API request failed with status'); - } - } catch (e) { - print('Exception occurred: $e'); - } - } - - @override - Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; - final screenHeight = MediaQuery.of(context).size.height; - - final crossAxisCount = 4; - final spacing = 20.0; // crossAxisSpacing - final totalSpacing = (crossAxisCount - 1) * spacing; - final itemWidth = (screenWidth - totalSpacing) / crossAxisCount; - - // Example: target card height - final itemHeight = screenHeight * 0.2; - - // Dynamic aspect ratio: - final aspectRatio = screenWidth / screenHeight; - - print("_PreEnrollmentState 4"); - // TODO: implement build - return Container( - // height: MediaQuery.of(context).size.height * 0.2, - - // height: 400, - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - // color: Colors.yellow.shade100, - borderRadius: BorderRadius.circular(16), - ), - // - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - 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 - ), - ) - : Flexible( - child: Container( - // color: Colors.redAccent.shade100, - // color: Colors.white, - // color: Colors.white, - // padding: EdgeInsets.symmetric(horizontal: 16.0), - // height: MediaQuery.of(context).size.height * 0.45, - // height: 400, - - child: Container( - child: getCardArrays.isEmpty - ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - 'assets/searchData.jpg', // Replace 'default_image.png' with your default image asset path - width: 200, - height: 200, - fit: BoxFit.cover, - ), - Text( - 'No Policy Mapping Found', - style: TextStyle( - fontSize: 16, - color: Colors.grey.shade600, - ), - ), - ], - ), - ) - : GridView.builder( - itemCount: getCardArrays.length, - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 20, - mainAxisSpacing: 20, - childAspectRatio: 2.4, - // childAspectRatio: aspectRatio, - // childAspectRatio: 2.1, - ), - itemBuilder: (context, index) { - return buildPolicyCard(getCardArrays[index]); - }, - ), - ), - ), - ), - ], - ), - ); - } - - Widget buildPolicyCard(Map policy) { - final mediaQuery = MediaQuery.of(context); - final devicePixelRatio = mediaQuery.devicePixelRatio; - final logicalWidth = 230 / devicePixelRatio; - final logicalHeight = 189 / devicePixelRatio; - - print("logicalWidth - $logicalWidth"); - print("logicalHeight - $logicalHeight"); - return InkWell( - onTap: () { - setState(() { - print("policytab - $policy"); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => hrPolicyDetails( - ClientId: policy['client_id'].toString(), // <-- from map - policyTypeId: - policy['policy_type_id'].toString(), // <-- from map - ClientPoliyId: policy['client_policy_id'].toString(), - clientBranchId: widget.enrollmentClientBranchId, - Token: widget.enrollToken, - TokenType: "pre", - cardType: policy['type'].toString(), - cardPolicyNo: policy['policy_no'].toString(), - cardInsurer_name: policy['insurer_short_name'].toString(), - cardPolicy_name: policy['policy_name'].toString(), - cardPolicy_ExpDate: policy['policy_expiry_date'].toString(), - ), - ), - ); - }); - }, - - // height: MediaQuery.of(context).size.height * 0.1, - // width: MediaQuery.of(context).size.height * 0.1, - // width: logicalWidth, - // height: logicalHeight, - child: Container( - margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), - // height: MediaQuery.of(context).size.height * 1, - // width: MediaQuery.of(context).size.height * 0.1, - // margin: EdgeInsets.only(bottom: 10.0), - decoration: BoxDecoration( - // color: Colors.yellow.shade50, - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - spreadRadius: 1, - offset: Offset(0, 0), // Equal shadow in all directions - ), - ], - ), - // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - height: MediaQuery.of(context).size.height * 0.03, - // color: Colors.green.shade100, - child: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${policy['type']} - ${policy['policy_no']} " ?? '', - style: const TextStyle( - fontFamily: "Inter", - fontWeight: FontWeight.w600, - fontSize: 12, - ), - ), - ], - ), - ], - ), - ), - // Spacer(), - SizedBox( - height: 5, - ), - Container( - // color: Colors.pink.shade50, - height: MediaQuery.of(context).size.height * 0.09, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _buildCountBox( - policy['membersCountOfDraft'].toString(), "Draft"), - Spacer(), - _buildCountBox(policy['membersCountOfEnrolled'].toString(), - "Enrolled"), - Spacer(), - _buildCountBox( - policy['totalMembersCount'].toString(), "Total"), - // Spacer(), - ], - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildCountBox(String count, String label) { - return Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - // width: 70, - // height: 40, - height: MediaQuery.of(context).size.height * 0.05, - width: MediaQuery.of(context).size.height * 0.1, - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0xFFDFF1F3), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - count, - style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), - ), - ), - // Spacer(), - const SizedBox(height: 8), - Text(label, - style: const TextStyle(fontSize: 10, color: Color(0xFF848484))), - ], - ); - } -} +// import 'package:firebase_auth/firebase_auth.dart'; +// import 'package:flutter/cupertino.dart'; +// 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 '../../presentation/hrPolicyDetails.dart'; +// import '../api_service.dart'; +// +// class PreEnrollment extends StatefulWidget { +// final String enrollmentClientId; +// final String enrollmentClientBranchId; +// final String enrollmentHrId; +// final String enrollToken; +// +// const PreEnrollment( +// {Key? key, +// required this.enrollmentClientId, +// required this.enrollmentClientBranchId, +// required this.enrollmentHrId, +// required this.enrollToken}); +// +// @override +// State createState() => _PreEnrollmentState(); +// } +// +// class _PreEnrollmentState extends State { +// late ApiService apiService; +// dynamic getCardArrays = []; +// int selectedIndex = 0; +// bool isLoading = false; +// // List> getCardArrays = [ +// // { +// // "client_policy_id": "415", +// // "client_id": "187", +// // "policy_type_id": "2", +// // "is_addon": "1", +// // "OpenForEnrollment": "0", +// // "inception_type": "1", +// // "policy_no": "GMC-2002/2022/2005", +// // "insurer_id": null, +// // "policy_expiry_date": "06-07-2026", +// // "type": "GMC", +// // "policy_name": "Group Medical Coverage", +// // "insurer_name": null, +// // "insurer_short_name": null, +// // "totalMembersCount": 17, +// // "membersCountOfEnrolled": 0, +// // "membersCountOfDraft": 17 +// // }, +// // { +// // "client_policy_id": 396, +// // "client_id": 58, +// // "policy_type_id": 4, +// // "is_addon": 2, +// // "OpenForEnrollment": 1, +// // "inception_type": 2, +// // "policy_no": "GMC-MA8596745566998855885", +// // "insurer_id": 1, +// // "type": "GMC - Topup", +// // "policy_name": "Group Medical Coverage Topup", +// // "insurer_name": "Life Insurance Corporation of India (LIC)", +// // "insurer_short_name": "LIC", +// // "totalMembersCount": 7, +// // "membersCountOfEnrolled": 0, +// // "membersCountOfDraft": 7 +// // }, +// // +// // ]; +// +// @override +// void initState() { +// super.initState(); +// apiService = ApiService(context); +// _loadData(); +// +// print("_PreEnrollmentState 1"); +// } +// +// Future _loadData() async { +// await getCashDepositDetails(widget.enrollmentClientBranchId, +// widget.enrollmentClientId, widget.enrollmentHrId, widget.enrollToken); +// } +// +// Future getCashDepositDetails( +// clintBranchId, clintID, hr_id, token) async { +// print("_PreEnrollmentState 2"); +// print('IN'); +// print("clintBranchId -$clintBranchId"); +// print("clintID -$clintID"); +// print("hr_id -$hr_id"); +// print("token -$token"); +// +// // isLoading = true; +// setState(() { +// isLoading = true; +// }); +// try { +// if (clintBranchId == null || clintID == null) { +// return; +// } +// final response = await apiService.getCashDepositDetailsToApi( +// clintID!, clintBranchId!, hr_id, token); +// +// // final response = await apiService.getCashDepositDetailsToApi( +// // clintID!, clintBranchId!, hr_id, token); +// print('IN1'); +// if (response['status'] == 'success') { +// +// setState(() { +// isLoading = false; +// print('response'); +// print(response['data']); +// +// print("_PreEnrollmentState 3"); +// // getCardArrays = []; +// getCardArrays = List>.from(response['data']); +// print('getCardArrays'); +// print(getCardArrays); +// }); +// +// print('IN2'); +// print("getCardArrays9 - $getCardArrays"); +// } else { +// setState(() { +// isLoading = false; +// }); +// +// print('API request failed with status'); +// } +// } catch (e) { +// print('Exception occurred: $e'); +// } +// } +// +// @override +// Widget build(BuildContext context) { +// final screenWidth = MediaQuery.of(context).size.width; +// final screenHeight = MediaQuery.of(context).size.height; +// +// final crossAxisCount = 4; +// final spacing = 20.0; // crossAxisSpacing +// final totalSpacing = (crossAxisCount - 1) * spacing; +// final itemWidth = (screenWidth - totalSpacing) / crossAxisCount; +// +// // Example: target card height +// final itemHeight = screenHeight * 0.2; +// +// // Dynamic aspect ratio: +// final aspectRatio = screenWidth / screenHeight; +// +// print("_PreEnrollmentState 4"); +// // TODO: implement build +// return Container( +// // height: MediaQuery.of(context).size.height * 0.2, +// +// // height: 400, +// padding: const EdgeInsets.all(16.0), +// decoration: BoxDecoration( +// color: Colors.white, +// // color: Colors.yellow.shade100, +// borderRadius: BorderRadius.circular(16), +// ), +// // +// child: Column( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// 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 +// ), +// ) +// : Flexible( +// child: Container( +// // color: Colors.redAccent.shade100, +// // color: Colors.white, +// // color: Colors.white, +// // padding: EdgeInsets.symmetric(horizontal: 16.0), +// // height: MediaQuery.of(context).size.height * 0.45, +// // height: 400, +// +// child: Container( +// child: getCardArrays.isEmpty +// ? Center( +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Image.asset( +// 'assets/searchData.jpg', // Replace 'default_image.png' with your default image asset path +// width: 200, +// height: 200, +// fit: BoxFit.cover, +// ), +// Text( +// 'No Policy Mapping Found', +// style: TextStyle( +// fontSize: 16, +// color: Colors.grey.shade600, +// ), +// ), +// ], +// ), +// ) +// : GridView.builder( +// itemCount: getCardArrays.length, +// gridDelegate: +// const SliverGridDelegateWithFixedCrossAxisCount( +// crossAxisCount: 4, +// crossAxisSpacing: 20, +// mainAxisSpacing: 20, +// childAspectRatio: 2.4, +// // childAspectRatio: aspectRatio, +// // childAspectRatio: 2.1, +// ), +// itemBuilder: (context, index) { +// return buildPolicyCard(getCardArrays[index]); +// }, +// ), +// ), +// ), +// ), +// ], +// ), +// ); +// } +// +// Widget buildPolicyCard(Map policy) { +// final mediaQuery = MediaQuery.of(context); +// final devicePixelRatio = mediaQuery.devicePixelRatio; +// final logicalWidth = 230 / devicePixelRatio; +// final logicalHeight = 189 / devicePixelRatio; +// +// print("logicalWidth - $logicalWidth"); +// print("logicalHeight - $logicalHeight"); +// return InkWell( +// onTap: () { +// setState(() { +// print("policytab - $policy"); +// Navigator.push( +// context, +// MaterialPageRoute( +// builder: (context) => hrPolicyDetails( +// ClientId: widget.enrollmentClientId, // <-- from map +// policyTypeId: +// policy['policy_type_id'].toString(), // <-- from map +// ClientPoliyId: policy['client_policy_id'].toString(), +// clientBranchId: widget.enrollmentClientBranchId, +// Token: widget.enrollToken, +// TokenType: "pre", +// cardType: policy['type'].toString(), +// cardPolicyNo: policy['policy_no'].toString(), +// cardInsurer_name: policy['insurer_short_name'].toString(), +// cardPolicy_name: policy['policy_name'].toString(), +// cardPolicy_ExpDate: policy['policy_expiry_date'].toString(), +// ), +// ), +// ); +// }); +// }, +// +// // height: MediaQuery.of(context).size.height * 0.1, +// // width: MediaQuery.of(context).size.height * 0.1, +// // width: logicalWidth, +// // height: logicalHeight, +// child: Container( +// margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), +// // height: MediaQuery.of(context).size.height * 1, +// // width: MediaQuery.of(context).size.height * 0.1, +// // margin: EdgeInsets.only(bottom: 10.0), +// decoration: BoxDecoration( +// // color: Colors.yellow.shade50, +// color: Colors.white, +// borderRadius: BorderRadius.circular(12), +// boxShadow: const [ +// BoxShadow( +// color: Colors.black12, +// blurRadius: 6, +// spreadRadius: 1, +// offset: Offset(0, 0), // Equal shadow in all directions +// ), +// ], +// ), +// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), +// child: Padding( +// padding: const EdgeInsets.all(12), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// height: MediaQuery.of(context).size.height * 0.03, +// // color: Colors.green.shade100, +// child: Row( +// children: [ +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text( +// "${policy['type']} - ${policy['policy_no']} " ?? '', +// style: const TextStyle( +// fontFamily: "Inter", +// fontWeight: FontWeight.w600, +// fontSize: 12, +// ), +// ), +// ], +// ), +// ], +// ), +// ), +// // Spacer(), +// SizedBox( +// height: 5, +// ), +// Container( +// // color: Colors.pink.shade50, +// height: MediaQuery.of(context).size.height * 0.09, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// _buildCountBox( +// policy['membersCountOfDraft'].toString(), "Draft"), +// Spacer(), +// _buildCountBox(policy['membersCountOfEnrolled'].toString(), +// "Enrolled"), +// Spacer(), +// _buildCountBox( +// policy['totalMembersCount'].toString(), "Total"), +// // Spacer(), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// ); +// } +// +// Widget _buildCountBox(String count, String label) { +// return Column( +// // mainAxisAlignment: MainAxisAlignment.spaceBetween, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// // width: 70, +// // height: 40, +// height: MediaQuery.of(context).size.height * 0.05, +// width: MediaQuery.of(context).size.height * 0.1, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: const Color(0xFFDFF1F3), +// borderRadius: BorderRadius.circular(8), +// ), +// child: Text( +// count, +// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), +// ), +// ), +// // Spacer(), +// const SizedBox(height: 8), +// Text(label, +// style: const TextStyle(fontSize: 10, color: Color(0xFF848484))), +// ], +// ); +// } +// } diff --git a/lib/service/multi_file_upload_widget.dart b/lib/service/multi_file_upload_widget.dart new file mode 100755 index 0000000..57d23f2 --- /dev/null +++ b/lib/service/multi_file_upload_widget.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import '../responsive.dart'; +import 'file_upload_service.dart'; + +class MultiFileUploadWidget extends StatefulWidget { + final bool forceMobile; + + const MultiFileUploadWidget({super.key, this.forceMobile = false}); + + @override + State createState() => _MultiFileUploadWidgetState(); + + static bool hasFiles = false; +} + +class _MultiFileUploadWidgetState extends State { + final fileService = FileUploadService(); + String? errorMessage; + + void _pickFiles() async { + final error = await fileService.pickFiles(maxFileSizeInMB: 10); // 5 MB limit + if (error != null) { + if (mounted) { + setState(() { + errorMessage = error; + }); + + // also show alert dialog for big error messages + // showDialog( + // context: context, + // builder: (ctx) => AlertDialog( + // title: const Text("File Upload Error"), + // content: Text(error), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(ctx), + // child: const Text("OK"), + // ), + // ], + // ), + // ); + } + } else { + setState(() { + errorMessage = null; + MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty; + }); + } + } + + void _removeFile(int index) { + fileService.removeFileAt(index); + setState(() { + MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty; + }); + } + + @override + Widget build(BuildContext context) { + final files = fileService.files; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.forceMobile || Responsive.isMobile(context)) ...[ + OutlinedButton.icon( + onPressed: _pickFiles, + icon: const Icon(Icons.file_upload_outlined, + color: Color(0xFF00999E), size: 24), + label: const Text( + "Upload Documents", + style: TextStyle( + fontSize: 14, + color: Colors.black, + ), + overflow: TextOverflow.ellipsis, + ), + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Color(0xFF00999E)), + ), + ), + const SizedBox(height: 6), + const Text( + "Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ] else ...[ + Row( + children: [ + OutlinedButton.icon( + onPressed: _pickFiles, + icon: const Icon(Icons.file_upload_outlined, + color: Color(0xFF00999E), size: 24), + label: const Text( + "Upload Documents", + style: TextStyle( + fontSize: 14, + color: Colors.black, + ), + overflow: TextOverflow.ellipsis, + ), + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Color(0xFF00999E)), + ), + ), + const SizedBox(width: 12), + const Expanded( + child: Text( + "Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)", + style: TextStyle(fontSize: 12, color: Colors.grey), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + // InkWell( + // onTap: _pickFiles, // ✅ SAME FUNCTION + // child: Container( + // width: double.infinity, + // padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), + // decoration: BoxDecoration( + // color: const Color(0xFFF9F9F9), + // borderRadius: BorderRadius.circular(12), + // border: Border.all( + // color: const Color(0xFF00A6A6), + // width: 1, + // style: BorderStyle.solid, // dotted look via color + spacing + // ), + // ), + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // /// Icon circle + // Container( + // padding: const EdgeInsets.all(10), + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // border: Border.all(color: const Color(0xFF00A6A6)), + // ), + // child: const Icon( + // Icons.insert_drive_file, + // size: 28, + // color: Color(0xFF00A6A6), + // ), + // ), + // + // const SizedBox(height: 10), + // + // // /// File name (static text – logic unchanged) + // // const Text( + // // "Sample_file.PDF", + // // style: TextStyle( + // // fontSize: 14, + // // fontWeight: FontWeight.w500, + // // ), + // // overflow: TextOverflow.ellipsis, + // // ), + // // + // // const SizedBox(height: 6), + // + // /// Helper text + // const Text( + // "(Supported formats: PDF, PNG, JPG, JPEG, HEIC | Max 10MB each)", + // style: TextStyle( + // fontSize: 11, + // color: Colors.grey, + // ), + // textAlign: TextAlign.center, + // ), + // ], + // ), + // ), + // ), + ], + + if (fileService.files.isEmpty && errorMessage == null) ...[ + const SizedBox(height: 4), + const Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + + if (errorMessage != null) ...[ + const SizedBox(height: 4), + Text( + errorMessage!, + style: const TextStyle(color: Colors.red, fontSize: 12), + ), + ], + + const SizedBox(height: 8), + ...fileService.files.asMap().entries.map((entry) { + final index = entry.key; + final uploaded = entry.value; // UploadedFile + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: Text(uploaded.file.name, style: const TextStyle(fontSize: 14)), + trailing: IconButton( + icon: const Icon(Icons.close, color: Colors.red), + onPressed: () => _removeFile(index), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0, bottom: 8.0, right: 8.0), + child: TextField( + controller: uploaded.controller, + decoration: const InputDecoration( + labelText: 'Enter document name', + border: OutlineInputBorder(), + isDense: true, + ), + ), + ), + ], + ); + }), + ], + ); + } +} diff --git a/lib/service/svg_service.dart b/lib/service/svg_service.dart new file mode 100644 index 0000000..c02046b --- /dev/null +++ b/lib/service/svg_service.dart @@ -0,0 +1,60 @@ +// svg_service.dart +class SvgService { + static const String dashboard = ''' + + + + '''; + + static const String policies = ''' + + + + '''; + + static const String cd = ''' + + + + '''; + + static const String claims = ''' + + + + + + + + + + + + '''; + + static const String logout = ''' + + + + '''; + + + static String getSvg(String svgName) { + switch (svgName) { + case 'dashboard': + return dashboard; + case 'policies': + return policies; + case 'cd': + return cd; + case 'claims': + return claims; + case 'logout': + return logout; + default: + return ''; + } + } + +// Add more SVG strings here as needed +} diff --git a/lib/service/token_storage_service.dart b/lib/service/token_storage_service.dart index a71c975..32da29e 100755 --- a/lib/service/token_storage_service.dart +++ b/lib/service/token_storage_service.dart @@ -1,16 +1,23 @@ -import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + class TokenStorageService { - static final TokenStorageService _instance = TokenStorageService._internal(); + static final TokenStorageService _instance = + TokenStorageService._internal(); factory TokenStorageService() => _instance; TokenStorageService._internal(); + // 🔐 Secure storage instance + static const FlutterSecureStorage _secureStorage = + FlutterSecureStorage(); + // Storage keys static const String _preEnrollmentKey = 'pre_enrollment_data'; static const String _postEnrollmentKey = 'post_enrollment_data'; static const String _selectedBranchKey = 'selected_branch'; static const String _decodedTokenKey = 'decoded_token'; + static const String _branchNameKey = 'branch_name'; // In-memory cache List? _preEnrollmentData; @@ -18,51 +25,64 @@ class TokenStorageService { Map? _selectedBranch; Map? _decodedToken; - // Initialize - Load data from storage + // 🔄 Initialize from secure storage Future initialize() async { - final prefs = await SharedPreferences.getInstance(); - - final preData = prefs.getString(_preEnrollmentKey); + final preData = await _secureStorage.read(key: _preEnrollmentKey); if (preData != null) { _preEnrollmentData = json.decode(preData); } - final postData = prefs.getString(_postEnrollmentKey); + final postData = await _secureStorage.read(key: _postEnrollmentKey); if (postData != null) { _postEnrollmentData = json.decode(postData); } - final branchData = prefs.getString(_selectedBranchKey); + final branchData = + await _secureStorage.read(key: _selectedBranchKey); if (branchData != null) { _selectedBranch = json.decode(branchData); } - final tokenData = prefs.getString(_decodedTokenKey); + final tokenData = + await _secureStorage.read(key: _decodedTokenKey); if (tokenData != null) { _decodedToken = json.decode(tokenData); } } - // Save enrollment data - Future saveEnrollmentData(List preData, List postData) async { + // 💾 Save enrollment data + Future saveEnrollmentData( + List preData, + List postData, + ) async { _preEnrollmentData = preData; _postEnrollmentData = postData; - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_preEnrollmentKey, json.encode(preData)); - await prefs.setString(_postEnrollmentKey, json.encode(postData)); + await _secureStorage.write( + key: _preEnrollmentKey, value: json.encode(preData)); + await _secureStorage.write( + key: _postEnrollmentKey, value: json.encode(postData)); } - // Get combined unique branches List> getCombinedBranches() { List> combined = []; Set seenTokens = {}; Set seenIds = {}; + // Helper: check valid token + bool hasValidToken(dynamic token) { + return token != null && + token.toString().trim().isNotEmpty; + } + // Add pre-enrollment data if (_preEnrollmentData != null) { for (var item in _preEnrollmentData!) { - String token = item['token']?.toString() ?? ''; + final token = item['token']; + + // ❌ SKIP if token is empty or null + if (!hasValidToken(token)) continue; + String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}'; if (seenIds.contains(uniqueId)) continue; @@ -78,7 +98,11 @@ class TokenStorageService { // Add post-enrollment data if (_postEnrollmentData != null) { for (var item in _postEnrollmentData!) { - String token = item['token']?.toString() ?? ''; + final token = item['token']; + + // ❌ SKIP if token is empty or null + if (!hasValidToken(token)) continue; + String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}'; if (seenIds.contains(uniqueId)) continue; @@ -94,42 +118,41 @@ class TokenStorageService { return combined; } - // Save selected branch and decode token - Future saveSelectedBranch(Map branch) async { + // 🌿 Save selected branch + decode JWT + Future saveSelectedBranch( + Map branch) async { _selectedBranch = branch; String token = branch['token']?.toString() ?? ''; - if (token.isNotEmpty) { - _decodedToken = _decodeJWT(token); - } else { - _decodedToken = null; - } + _decodedToken = token.isNotEmpty ? _decodeJWT(token) : null; - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_selectedBranchKey, json.encode(branch)); + await _secureStorage.write( + key: _selectedBranchKey, value: json.encode(branch)); if (_decodedToken != null) { - await prefs.setString(_decodedTokenKey, json.encode(_decodedToken)); + await _secureStorage.write( + key: _decodedTokenKey, + value: json.encode(_decodedToken)); } - // ✅ Save branch name - String branchName = branch['branch_name']?.toString() ?? ''; - await prefs.setString('branchName', branchName); + await _secureStorage.write( + key: _branchNameKey, + value: branch['branch_name']?.toString() ?? '', + ); } - // Decode JWT token + // 🔓 Decode JWT Map? _decodeJWT(String token) { try { final parts = token.split('.'); if (parts.length != 3) return null; - final payload = parts[1]; - var normalized = base64Url.normalize(payload); - var decoded = utf8.decode(base64Url.decode(normalized)); + final payload = base64Url.normalize(parts[1]); + final decoded = utf8.decode(base64Url.decode(payload)); return json.decode(decoded); } catch (e) { - print('Error decoding token: $e'); + print('JWT decode error: $e'); return null; } } @@ -139,23 +162,131 @@ class TokenStorageService { Map? getDecodedToken() => _decodedToken; String? getCurrentToken() => _selectedBranch?['token']; - bool isLoggedIn() { - return _selectedBranch != null && - _selectedBranch!['token'] != null && - _selectedBranch!['token'].toString().isNotEmpty; - } + bool isLoggedIn() => + _selectedBranch != null && + _selectedBranch!['token'] != null && + _selectedBranch!['token'].toString().isNotEmpty; - // Clear all data (logout) + // 🚪 Logout (clear everything) Future clearAll() async { _preEnrollmentData = null; _postEnrollmentData = null; _selectedBranch = null; _decodedToken = null; - final prefs = await SharedPreferences.getInstance(); - await prefs.remove(_preEnrollmentKey); - await prefs.remove(_postEnrollmentKey); - await prefs.remove(_selectedBranchKey); - await prefs.remove(_decodedTokenKey); + await _secureStorage.deleteAll(); } -} \ No newline at end of file + + Future saveDecodedSessionData( + Map decodedToken, + String token, + ) async { + // Handle allowed_modules safely + dynamic allowedModules = decodedToken['allowed_modules']; + if (allowedModules is String) { + allowedModules = jsonDecode(allowedModules); + } + + await _secureStorage.write( + key: 'empClientBranchId', + value: decodedToken['post_branch_id']?.toString()); + + await _secureStorage.write( + key: 'empPrimaryId', + value: decodedToken['post_hr_id']?.toString()); + + await _secureStorage.write( + key: 'empClientId', + value: decodedToken['post_client_id']?.toString()); + + await _secureStorage.write( + key: 'empHrId', + value: decodedToken['post_hr_id']?.toString()); + + await _secureStorage.write( + key: 'empAllowed_modules', + value: jsonEncode(allowedModules?['post'] ?? [])); + + // ================= PRE (Enrollment) ================= + + await _secureStorage.write( + key: 'enrollmentEmpClientBranchId', + value: decodedToken['pre_branch_id']?.toString()); + + await _secureStorage.write( + key: 'enrollmentEmpPrimaryId', + value: decodedToken['pre_hr_id']?.toString()); + + await _secureStorage.write( + key: 'enrollmentClient_id', + value: decodedToken['pre_client_id']?.toString()); + + await _secureStorage.write( + key: 'enrollmentHrId', + value: decodedToken['pre_hr_id']?.toString()); + + await _secureStorage.write( + key: 'enrollmentAllowed_modules', + value: jsonEncode(allowedModules?['pre'] ?? [])); + + // ================= TOKEN ================= + await _secureStorage.write(key: 'token', value: token); + } + + Future readValue(String key) async { + return await _secureStorage.read(key: key); + } + + Future writeValue(String key, String value) async { + await _secureStorage.write(key: key, value: value); + } + + + Future clearBranchSession() async { + final keysToRemove = [ + 'selected_branch', + 'decoded_token', + 'clientLogo', + 'clientName', + 'empAllowed_modules', + 'empClientBranchId', + 'empClientId', + 'empEmail', + 'empHrId', + 'empPrimaryId', + 'enrollmentAllowed_modules', + 'enrollmentClient_id', + 'enrollmentEmpClientBranchId', + 'enrollmentEmpPrimaryId', + 'enrollmentHrId', + 'token', + ]; + + for (final key in keysToRemove) { + await _secureStorage.delete(key: key); + } + } + + Future resetSessionAndSwitchBranch( + Map newBranch, + ) async { + // 1️⃣ Clear ONLY branch/session related keys + await clearBranchSession(); + + // 2️⃣ Save selected branch + await saveSelectedBranch(newBranch); + + // 3️⃣ Rebuild decoded session data from token + final token = newBranch['token']?.toString(); + if (token != null && token.isNotEmpty) { + final decoded = _decodeJWT(token); + if (decoded != null) { + await saveDecodedSessionData(decoded, token); + } + } + + // 4️⃣ Update in-memory cache + _selectedBranch = newBranch; + } + +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index fc6e2af..c203066 100755 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) smart_auth_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin"); smart_auth_plugin_register_with_registrar(smart_auth_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 4ebc56f..e87a02c 100755 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux smart_auth url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ff40429..ca99b4f 100755 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import file_picker import firebase_auth import firebase_core +import flutter_secure_storage_darwin import google_sign_in_ios import path_provider_foundation import shared_preferences_foundation @@ -18,6 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.yaml b/pubspec.yaml index d73478e..ff2d1d0 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,11 @@ dependencies: firebase_auth_web: ^5.12.4 archive: ^3.4.9 dropdown_search: ^6.0.2 + flutter_secure_storage: ^10.0.0 + flutter_svg: ^2.2.3 + pdf: ^3.11.3 + dotted_border: ^3.1.0 + dropdown_button2: ^2.3.9 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 226252c..0aac551 100755 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); SmartAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SmartAuthPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 3ef1798..ca30f87 100755 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_auth firebase_core + flutter_secure_storage_windows smart_auth url_launcher_windows )