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'; import 'hrPolicyDetails.dart'; import 'package:nhancepolicy/logger.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(); String localClientId = ''; String localPolicyTypeId = ''; String localClientPolicyId = ''; String localClientBranchId = ''; String localToken = ''; String localTokenType = ''; String localCardType = ''; String localCardPolicyNo = ''; String localCardInsurerName = ''; String localCardPolicyName = ''; String localCardPolicyExpDate = ''; String localTotalPremium = ''; 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 = 6; 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); restoreUploadData().then((_) { _loadToken(); getFileListDetails(); }); } @override void dispose() { super.dispose(); html.window.localStorage.remove('fileBytes'); } Future _loadToken() async { final token = localToken.isNotEmpty ? localToken : await tokenService.readValue('upload_Token'); if (token != null && token.isNotEmpty) { setState(() { _token = token; }); } else { ToastHelper.showErrorToast(context, 'Session Out'); Navigator.pushReplacementNamed(context, 'hrLogin'); } } Future restoreUploadData() async { localClientId = widget.ClientId.isNotEmpty ? widget.ClientId : await tokenService.readValue('upload_ClientId') ?? ''; localPolicyTypeId = widget.policyTypeId.isNotEmpty ? widget.policyTypeId : await tokenService.readValue('upload_policyTypeId') ?? ''; localClientPolicyId = widget.ClientPoliyId.isNotEmpty ? widget.ClientPoliyId : await tokenService.readValue('upload_ClientPoliyId') ?? ''; localClientBranchId = widget.clientBranchId.isNotEmpty ? widget.clientBranchId : await tokenService.readValue('upload_clientBranchId') ?? ''; localToken = widget.Token.isNotEmpty ? widget.Token : await tokenService.readValue('upload_Token') ?? ''; localTokenType = widget.TokenType.isNotEmpty ? widget.TokenType : await tokenService.readValue('upload_TokenType') ?? ''; localCardType = widget.cardType.isNotEmpty ? widget.cardType : await tokenService.readValue('upload_cardType') ?? ''; localCardPolicyNo = widget.cardPolicyNo.isNotEmpty ? widget.cardPolicyNo : await tokenService.readValue('upload_cardPolicyNo') ?? ''; localCardInsurerName = widget.cardInsurer_name.isNotEmpty ? widget.cardInsurer_name : await tokenService.readValue('upload_cardInsurer_name') ?? ''; localCardPolicyName = widget.cardPolicy_name.isNotEmpty ? widget.cardPolicy_name : await tokenService.readValue('upload_cardPolicy_name') ?? ''; localCardPolicyExpDate = widget.cardPolicy_ExpDate.isNotEmpty ? widget.cardPolicy_ExpDate : await tokenService.readValue('upload_cardPolicy_ExpDate') ?? ''; localTotalPremium = widget.total_premium.isNotEmpty ? widget.total_premium : await tokenService.readValue('upload_total_premium') ?? ''; } Future clearUploadStorage() async { await tokenService.removeValue('upload_ClientId'); await tokenService.removeValue('upload_policyTypeId'); await tokenService.removeValue('upload_ClientPoliyId'); await tokenService.removeValue('upload_clientBranchId'); await tokenService.removeValue('upload_Token'); await tokenService.removeValue('upload_TokenType'); await tokenService.removeValue('upload_cardType'); await tokenService.removeValue('upload_cardPolicyNo'); await tokenService.removeValue('upload_cardInsurer_name'); await tokenService.removeValue('upload_cardPolicy_name'); await tokenService.removeValue('upload_cardPolicy_ExpDate'); await tokenService.removeValue('upload_total_premium'); } // 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 // logDebug('File name: ${file.name}'); // logDebug('File size: ${file.size}'); // logDebug('File bytes: $fileBytes'); // _processExcelData(fileBytes); // } else { // // User canceled the picker // } // } void _uploadFile(importPolicyName) async { logDebug('Test'); if (kIsWeb) { logDebug('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; logDebug('File Name: $fileName'); logDebug('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) logDebug('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')) { logDebug('csv'); dataArray = decodeCSVData(fileBytes); } else { throw UnsupportedError('Unsupported file format: $fileName'); } logDebug('_processExcelData'); // Decode the Excel file and extract relevant data // Assuming dataArray is your array containing Excel data // List> dataArray = decodeExcelData(fileBytes); logDebug(dataArray); // logDebug(dataArray[0].toString()); // Extract Name, Age, and City from the array logDebug(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) { // logDebug(dataMap); validationArray.add(dataMap); } else { // logDebug(dataMap); extractedData.add(dataMap); } } // Do something with extracted data (e.g., display in UI) originalData = extractedData; filteredData = List.from(originalData); logDebug('filteredData'); logDebug(filteredData); logDebug(validationArray); validationArray[0].forEach((key, value) { logDebug(key); logDebug(value); if (key.toString().trim().toLowerCase() != value.toString().trim().toLowerCase()) { // If key and value are not equal, increment mismatch count columnIndexMismatchCount++; logDebug('columnIndexMismatchCount: $value'); } if (value.toString() == 'null') { // If value is null, increment missing count columnMissingCount++; logDebug('Value: $value'); } }); logDebug('columnIndexMismatchCount: $columnIndexMismatchCount'); logDebug('columnMissingCount: $columnMissingCount'); nonExcelFilteredData = filteredData.where((item) { final relation = item['Relation']; return relation != null && relation.toString().trim().toLowerCase() != 'self'; }).toList(); logDebug('nonExcelFilteredData'); logDebug(nonExcelFilteredData); logDebug(nonExcelFilteredData.length); if (argumentsData['type'] == 'GPA') { if (nonExcelFilteredData.length > 0) { logDebug('nonSelf'); invalidRelationships = nonExcelFilteredData.length; } else { logDebug('Self'); invalidRelationships = nonExcelFilteredData.length; } logDebug('invalidRelationships: $invalidRelationships'); } else { invalidRelationships = 0; } int invalidDobCount = countInvalidDobs(filteredData); logDebug('Number of invalid DOBs: $invalidDobCount'); dobAgeCheckCount = invalidDobCount; } else { logDebug('Some Column is Missing'); var columnMissingCount = 11 - dataArray[0].length; missingColumnErrorMsg = columnMissingCount; logDebug(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) { logDebug('decodeExcelData'); // Create an Excel instance from the fileBytes final excel = Excel.decodeBytes(fileBytes); logDebug('decodeExcelData'); logDebug(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; // } logDebug(entry['Relation']); if ((entry['Relation'].toString() == 'Son' || entry['Relation'].toString() == 'Daughter')) { if (DateTime.now().difference(dob).inDays > 25 * 365) { logDebug('child $dob'); invalidCount++; } } else if ((entry['Relation'] != 'Son' && entry['Relation'] != 'Daughter')) { if (DateTime.now().difference(dob).inDays < 18 * 365) { logDebug('others $dob'); invalidCount++; } } } return invalidCount; } void _dragAndDropFile(html.File file) async { logDebug('file'); logDebug(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 logDebug(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; // }); // }); logDebug('submit'); logDebug(fileBytes); if (fileBytes == null) { logDebug('return'); return; // No file selected } else { logDebug('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'; logDebug('else'); // Create a multipart request final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); logDebug('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)); logDebug('Filename: $fileName'); request.files.add(http.MultipartFile.fromBytes( 'file', fileBytes, filename: fileName ?? 'default_filename.xlsx', )); request.fields['client_id'] = localClientId; // if (policyFirstPart == 'GPA') { request.fields['policy_id'] = localClientPolicyId; request.fields['client_branch_id'] = localClientBranchId; 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'; // } logDebug('request : $request'); // Send the request final response = await request.send(); logDebug('else'); // Read response stream as a string final responseString = await response.stream.bytesToString(); logDebug('else'); // Check the status code of the response if (response.statusCode == 200) { isLoading = false; Map data = json.decode(responseString); if (data['status'] == false) { ToastHelper.showErrorToast(context, data['message']); logDebug('Table'); setState(() { isSuccess = true; successContent = data['message']; excelValidationStaus = 0; resetErrorCount(); handleImportAction(); getFileListDetails(); }); } else { setState(() { isLoading = false; handleImportAction(); }); ToastHelper.showErrorToast2(context, "", data['message']); setState(() { resetErrorCount(); getFileListDetails(); }); // ToastHelper.showErrorToast(context, data['message']); logDebug('Table'); } } else { setState(() { isLoading = false; }); // ToastHelper.showSuccessToast( // context, 'Failed to upload file: ${response.reasonPhrase}'); ToastHelper.showErrorToast(context, 'Something went wrong'); logDebug('Failed to upload file: ${response.reasonPhrase}'); } } } Future handleImportAction() async { logDebug('handleImportAction'); final postId = await tokenService.readValue('empHrId'); final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); var activity = "import_enrollempdata"; dynamic response; logDebug('postId - $postId'); logDebug('preId - $preId'); logDebug('activity - $activity'); try { logDebug('10'); response = await apiService.getImportLogHrActivity( postId!, preId!, localToken, activity); if (response['status'] == 'success') { logDebug('Request success'); } else { // ToastHelper.showWarningToast( // context, 'Request failed with status: ${response.statusCode}'); logDebug('Request failed with status: ${response['code']}'); } } catch (e) { logDebug('Exception occurred: $e'); } } Future getFileListDetails() async { final enrollmentPrimaryId = await tokenService.readValue('enrollmentEmpPrimaryId'); final enrollmentClientId = await tokenService.readValue('enrollmentClient_id'); logDebug('9'); try { final response = await apiService.getFileListToApi(enrollmentPrimaryId, localCardPolicyNo, enrollmentClientId, localToken, localTokenType); if (response['status'] == true) { logDebug('getThrFileList'); setState(() { getThrFileList = List>.from(response['data']); originalData = getThrFileList; filteredData = List.from(originalData); logDebug('filteredData'); logDebug(filteredData); }); } else { logDebug('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); logDebug('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(localToken); // logDebug('check 1'); // if (response['status'] == 'success') { // final url = response['data']; // _launchURL(url); // } else { // ToastHelper.showErrorToast(context, '⚠️ Unknown response format'); // logDebug('⚠️ Unknown response format'); // } // } // // Future _launchURL(String url) async { // logDebug('url $url'); // try { // final Uri uri = Uri.parse(url); // await launchUrl(uri, mode: LaunchMode.externalApplication); // } catch (e) { // logDebug('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, localToken); final apiurl = Environment.apiUrl; final String url = '$apiurl/hrFileDownload?id=$id'; final token = localToken; 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 { logDebug("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 { logDebug("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( tooltip: 'Previous Page', onPressed: () async { if (Navigator.canPop(context)) { Navigator.pop(context); return; } final clientId = await tokenService.readValue('hr_ClientId') ?? ''; final policyTypeId = await tokenService.readValue('hr_policyTypeId') ?? ''; final clientPolicyId = await tokenService.readValue('hr_ClientPoliyId') ?? ''; final clientBranchId = await tokenService.readValue('hr_clientBranchId') ?? ''; final token = await tokenService.readValue('hr_Token') ?? ''; final tokenType = await tokenService.readValue('hr_TokenType') ?? ''; final cardType = await tokenService.readValue('hr_cardType') ?? ''; final policyNo = await tokenService.readValue('hr_cardPolicyNo') ?? ''; final insurer = await tokenService .readValue('hr_cardInsurer_name') ?? ''; final policyName = await tokenService .readValue('hr_cardPolicy_name') ?? ''; final expDate = await tokenService .readValue('hr_cardPolicy_ExpDate') ?? ''; final totalPremium = await tokenService.readValue('hr_total_premium') ?? ''; final bulkDownload = await tokenService.readValue( 'hr_is_ecard_bulk_download_for_employee') ?? '0'; Navigator.pushReplacement( context, MaterialPageRoute( settings: const RouteSettings(name: 'hrPolicyDetails'), builder: (_) => hrPolicyDetails( ClientId: clientId, policyTypeId: policyTypeId, ClientPoliyId: clientPolicyId, clientBranchId: clientBranchId, Token: token, TokenType: tokenType, cardType: cardType, cardPolicyNo: policyNo, cardInsurer_name: insurer, cardPolicy_name: policyName, cardPolicy_ExpDate: expDate, total_premium: totalPremium, is_ecard_bulk_download_for_employee: int.tryParse(bulkDownload) ?? 0, ), ), ); }, icon: const Icon( Icons.arrow_back_ios, size: 18, color: Colors.black, ), ), const SizedBox(width: 6), Container( // color: Colors.redAccent.shade100, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "${localCardType} - ${localCardPolicyNo} " ?? '', style: GoogleFonts.poppins( color: Colors.black, fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( localTokenType == 'pre' ? "${localCardPolicyName} (${localCardPolicyExpDate})" : "${localCardInsurerName} - ${localCardPolicyName} (${localCardPolicyExpDate})", style: GoogleFonts.poppins( color: Colors.grey, fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ), ), ], ), SizedBox(height: 20), Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LayoutBuilder( builder: (context, constraints) { final narrow = constraints.maxWidth < 580; if (narrow) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _dateField( label: 'Enrolment Open Date', controller: openDateController, onTap: () async { final picked = await showDatePicker( context: context, firstDate: DateTime(2000), lastDate: DateTime.now(), initialDate: DateTime.now(), ); if (picked != null) { final formatted = DateFormat('dd-MM-yyyy') .format(picked); if (openDateController.text != formatted) { closeDateController.clear(); } openDateController.text = formatted; } }, ), const SizedBox(height: 16), _dateField( label: 'Enrolment Close Date', controller: closeDateController, onTap: () async { if (openDateController.text.isEmpty) { ToastHelper.showErrorToast( context, 'Please select Enrolment Open Date first', ); return; } final openDate = DateFormat('dd-MM-yyyy').parse( openDateController.text, ); final picked = await showDatePicker( context: context, firstDate: openDate, lastDate: DateTime(2100), initialDate: openDate, ); if (picked != null) { closeDateController.text = DateFormat('dd-MM-yyyy') .format(picked); } }, ), ], ); } return Row( children: [ SizedBox( width: 260, child: _dateField( label: 'Enrolment Open Date', controller: openDateController, onTap: () async { final picked = await showDatePicker( context: context, firstDate: DateTime(2000), lastDate: DateTime.now(), initialDate: DateTime.now(), ); if (picked != null) { final formatted = DateFormat('dd-MM-yyyy') .format(picked); if (openDateController.text != formatted) { closeDateController.clear(); } openDateController.text = formatted; } }, ), ), const SizedBox(width: 16), SizedBox( width: 260, child: _dateField( label: 'Enrolment Close Date', controller: closeDateController, onTap: () async { if (openDateController.text.isEmpty) { ToastHelper.showErrorToast( context, 'Please select Enrolment Open Date first', ); return; } final openDate = DateFormat('dd-MM-yyyy').parse( openDateController.text, ); final picked = await showDatePicker( context: context, firstDate: openDate, lastDate: DateTime(2100), initialDate: openDate, ); 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: Tooltip( message: 'Upload', // The text that appears on hover 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: Tooltip( message: 'Upload', // The text that appears on hover 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), Column( children: [ _buildFileUploadedGrid(), const SizedBox(height: 16), _buildPagination(context), ], ), ], ), ), ), ], ), ); } Widget _buildFileUploadedGrid() { if (filteredData.isEmpty) { return const Center(child: Text('No uploaded files')); } return LayoutBuilder( builder: (context, constraints) { final width = constraints.maxWidth; final crossAxisCount = width < 640 ? 1 : 2; const mainAxisExtent = 88.0; return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(16), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: 16, mainAxisSpacing: 16, mainAxisExtent: mainAxisExtent, ), itemCount: _paginatedData.length, itemBuilder: (context, index) { final item = _paginatedData[index]; return _buildFileCard(item); }, ); }, ); } // 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: 10), decoration: BoxDecoration( color: const Color(0xFFEFF9FA), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF9AD6DB)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( height: 40, width: 40, 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: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Text( item['file_name'] ?? '-', maxLines: 1, overflow: TextOverflow.ellipsis, style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: const Color(0xFF101010), ), ), const SizedBox(height: 2), Text.rich( 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)), ), ], ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 8), Row( mainAxisSize: MainAxisSize.min, children: [ if (item['file_error_status'] == '1') Tooltip( message: 'Info', child: InkWell( onTap: () async { final String? token = await tokenService.getCurrentToken(); final String? enrollmentClient_id = await tokenService .readValue('enrollmentClient_id'); final String? enrollmentEmpClientBranchId = await tokenService .readValue('enrollmentEmpClientBranchId'); if (token == null || enrollmentClient_id == null || enrollmentEmpClientBranchId == null) { debugPrint( '❌ Missing required data for navigation $token'); return; } if (!context.mounted) return; await openExcelErrorScreenIfAvailable( context: context, apiService: apiService, fileId: item['id'].toString(), tokenType: 'pre', clientId: enrollmentClient_id, policyNo: item['policy_no']?.toString() ?? '', action: item['file_action']?.toString() ?? '', createdAt: item['created_at']?.toString() ?? '', clientBranchId: enrollmentEmpClientBranchId, token: token, ); }, child: const Icon( Icons.error, size: 16, color: Colors.red, ), ), ), const SizedBox(width: 8), _buildStatusChip(item['status']), const SizedBox(width: 8), Tooltip( message: 'Download', child: InkWell( onTap: () { getHrFileDownload(item['id'], item['file_name']); }, child: Container( height: 28, width: 28, decoration: BoxDecoration( color: Color(0xFFC5F2F4), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFF76CED2)), ), child: const Icon( Icons.file_download_outlined, color: Color(0xFF1D1B20), size: 20, ), ), ), ), ], ), ], ), ); } 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) { // 1. Calculate the range of entries being shown final totalItems = filteredData.length; final int startEntry = totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1; int endEntry = _currentPage * _rowsPerPage; if (endEntry > totalItems) endEntry = totalItems; final totalPages = (filteredData.length / _rowsPerPage).ceil(); const visiblePageCount = 6; List getVisiblePages() { if (totalPages <= visiblePageCount) { return List.generate(totalPages, (i) => i + 1); } if (_currentPage <= 3) { return [1, 2, 3, 4, 5]; } if (_currentPage >= totalPages - 2) { return [ totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages ]; } return [ _currentPage - 2, _currentPage - 1, _currentPage, _currentPage + 1, _currentPage + 2, ]; } List visiblePages = getVisiblePages(); Widget paginationControls = Row( mainAxisSize: MainAxisSize.min, children: [ DropdownButton( value: _rowsPerPage, items: [6, 10, 15, 20, 50].map((int value) { return DropdownMenuItem( value: value, child: Text( ' $value ', style: GoogleFonts.poppins(fontSize: 15), ), ); }).toList(), onChanged: (newValue) { setState(() { _rowsPerPage = newValue!; _currentPage = 1; }); }, ), IconButton( onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, icon: const Icon(Icons.chevron_left), ), if (!visiblePages.contains(1)) Row( children: [ _buildPageButton(1), const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('...'), ), ], ), for (int page in visiblePages) _buildPageButton(page), if (!visiblePages.contains(totalPages) && totalPages > 0) Row( children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('...'), ), _buildPageButton(totalPages), ], ), IconButton( onPressed: _currentPage < totalPages ? () => setState(() => _currentPage++) : null, icon: const Icon(Icons.chevron_right), ), ], ); final showingText = Text( 'Showing $startEntry to $endEntry of $totalItems entries', style: GoogleFonts.poppins( fontSize: 13, color: const Color(0xFF585757), fontWeight: FontWeight.w400, ), ); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: LayoutBuilder( builder: (context, constraints) { final narrow = constraints.maxWidth < 720; if (narrow) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ showingText, const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: paginationControls, ), ), ], ); } return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ showingText, Flexible( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: paginationControls, ), ), ], ); }, ), ); } 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'; } } }