import 'dart:typed_data'; import 'package:flutter/material.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'; import 'package:nhancepolicy/logger.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; final String allocgType; 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, this.allocgType = ''}) : super(key: key); @override State createState() => _postFileUploadState(); } class _postFileUploadState 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 = ''; String localAllocgType = ''; 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']; bool showSampleButton = false; String? currentApiValue; // To store the 'value' for the 2nd param 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); } 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); restoreUploadData().then((_) { _loadToken(); getFileUploadMasterDetails(); 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') ?? ''; localAllocgType = widget.allocgType; } 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 // } // } Future getFileUploadMasterDetails() async { logDebug('9'); try { final response = await apiService.getFileUploadMastersToApi(localToken,localAllocgType); if (response['status'] == true) { logDebug('getFileUploadMasterList1'); setState(() { final actions = Map.from(response['data']['actions']); setState(() { getFileUploadMasterList = actions.entries .map((e) => {"key": e.key, "value": e.value}) .toList(); logDebug('getFileUploadMasterList: $getFileUploadMasterList'); }); logDebug('getFileUploadMasterList'); logDebug(getFileUploadMasterList); }); } else { logDebug('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); logDebug('Exception occurred: $e'); } finally { setState(() { // _isLoading = false; }); } } Future getFileListDetails() async { empPrimaryId = await tokenService.readValue('empPrimaryId'); empClientId = await tokenService.readValue('empClientId'); logDebug('9'); try { final response = await apiService.getFileListToApi(empPrimaryId, localCardPolicyNo, empClientId, localToken, localTokenType); if (response['status'] == 'success') { 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; }); } } Future getHrFileDownload(id, file_name) async { // final http.Response response = await apiService.getHrFileDownloadToApi(id, localToken); logDebug("**********-------*****"); final encryptClientId = localClientId; logDebug(encryptClientId); final apiurl = Environment.apiUrlPost; final String url = '$apiurl/hrFileDownload?id=$id&cliend_id=$encryptClientId'; 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); ToastHelper.showSuccessToast(context, 'File Downloaded Successfully'); } catch (e) { throw Exception('Error parsing response: $e'); } } else { ToastHelper.showErrorToast(context, 'Failed to download'); logDebug("Download failed with status: ${response.statusCode}"); } } Future downloadPostSampleFile(String apiParam) async { logDebug("fun Sam f - in"); final apiurl = Environment.apiUrlPost; final String url = '$apiurl/downloadSampleExcel/$apiParam'; 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("fun sam f - ${response.statusCode}"); final apiContentType = response.headers['content-type'] ?? ''; final contentDisposition = response.headers['content-disposition'] ?? ''; final excelContentType = apiContentType.contains('spreadsheetml') || apiContentType.contains('ms-excel') ? apiContentType : 'application/vnd.ms-excel'; final utf8FileNameMatch = RegExp( "filename\\*=UTF-8''([^;]+)", caseSensitive: false, ).firstMatch(contentDisposition); final plainFileNameMatch = RegExp( 'filename="?([^";]+)"?', caseSensitive: false, ).firstMatch(contentDisposition); final rawFileName = utf8FileNameMatch?.group(1) ?? plainFileNameMatch?.group(1); final fileName = rawFileName != null && rawFileName.trim().isNotEmpty ? Uri.decodeComponent(rawFileName.trim()) : '${apiParam}_sample_file.xlsx'; // ✅ Create a blob from the response body bytes final blob = html.Blob([response.bodyBytes], excelContentType); // ✅ Generate a download URL final url = html.Url.createObjectUrlFromBlob(blob); // ✅ Trigger file download with API-provided filename final anchor = html.AnchorElement(href: url); anchor.setAttribute('download', fileName); anchor.click(); // ✅ Revoke the URL to free memory html.Url.revokeObjectUrl(url); ToastHelper.showSuccessToast(context, 'File Downloaded Successfully'); } catch (e) { logDebug("fun sam f - fail"); throw Exception('Error parsing response: $e'); } } else { ToastHelper.showErrorToast(context, 'Failed to download'); logDebug("Download failed with status: ${response.statusCode}"); } } // Future downloadPostSampleFile(String apiParam) async { // logDebug("fun Sam f - in"); // // final apiurl = Environment.apiUrlPost; // final String url = '$apiurl/downloadSampleExcel/$apiParam'; // 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("fun sam f - ${response.statusCode}"); // // final apiContentType = response.headers['content-type'] ?? ''; // final excelContentType = // apiContentType.contains('spreadsheetml') || // apiContentType.contains('ms-excel') // ? apiContentType // : 'application/vnd.ms-excel'; // // // ✅ Create a blob from the response body bytes // final blob = html.Blob([response.bodyBytes], excelContentType); // // // ✅ Generate a download URL // final url = html.Url.createObjectUrlFromBlob(blob); // // // ✅ Trigger file download without assigning filename // final anchor = html.AnchorElement(href: url); // anchor.setAttribute('download', ''); // anchor.click(); // // // ✅ Revoke the URL to free memory // html.Url.revokeObjectUrl(url); // // ToastHelper.showSuccessToast(context, 'File Downloaded Successfully'); // } catch (e) { // logDebug("fun sam f - fail"); // throw Exception('Error parsing response: $e'); // } // } else { // ToastHelper.showErrorToast(context, 'Failed to download'); // logDebug("Download failed with status: ${response.statusCode}"); // } // } void _uploadFile() 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', ); 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; 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.'); } } 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); } Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { empPrimaryId = await tokenService.readValue('empPrimaryId'); // Future.delayed(Duration(seconds: 3), () { setState(() { isLoading = true; }); // }); logDebug('submit'); logDebug(fileBytes); if (fileBytes == null) { ToastHelper.showErrorToast(context, 'Please upload file'); logDebug('return'); return; // No file selected } else { logDebug('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'; 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.files.add(http.MultipartFile.fromBytes( 'file_name', fileBytes, filename: fileName ?? 'default_filename.xlsx', )); logDebug('clintID: $clintID'); request.fields['client_id'] = localClientId; request.fields['policy_no'] = localCardPolicyNo; request.fields['client_branch_id'] = localClientBranchId; request.fields['file_action'] = selectedKey!; // request.fields['status'] = selectedKey!; request.fields['created_by'] = empPrimaryId; request.fields['policy_id'] = localClientPolicyId; // "client_id": 1, // "client_branch_id": 2, // "policy_no": "POL123456", // "file_action": , // "status": "inception", // "created_by": 10 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(responseString); Map data = json.decode(responseString); if (data['status'] == true) { logDebug('upload success'); setState(() { isLoading = false; }); ToastHelper.showSuccessToast(context, data['message']); setState(() { selectedValue = null; selectedKey = null; resetErrorCount(); getFileListDetails(); }); } else { setState(() { getFileListDetails(); isLoading = false; selectedValue = null; selectedKey = null; resetErrorCount(); }); ToastHelper.showErrorToast(context, data['message']); } } } resetErrorCount() { setState(() { fileBytes = null; fileName = null; }); } void search(String query) { logDebug(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(); }); } logDebug(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( 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), Expanded( 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), ), ], ), ), // Visibility toggles based on dropdown selection Visibility( visible: showSampleButton, child: Padding( padding: const EdgeInsets.only(left: 10), child: SizedBox( child: ElevatedButton( onPressed: () { // Pass the dynamic value to the function downloadPostSampleFile(currentApiValue ?? ''); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE26728), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), child: Text( 'Sample Excel', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white), ), ), ), ), ), ], ), SizedBox(height: 20), Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LayoutBuilder( builder: (context, constraints) { final narrow = constraints.maxWidth < 640; final fileActionDropdown = buildStyledDropdown( label: 'Select File Action', value: selectedKey, items: getFileUploadMasterList, onChanged: (val) { setState(() { selectedKey = val; final selectedItem = getFileUploadMasterList.firstWhere( (e) => e['key'] == val, ); selectedValue = selectedItem['value']; currentApiValue = selectedItem['key']; showSampleButton = true; }); }, ); final uploadField = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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), 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, ), ], ), ), ); }, ), ], ); if (narrow) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ fileActionDropdown, const SizedBox(height: 16), uploadField, ], ); } return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 5, child: fileActionDropdown, ), const SizedBox(width: 16), Expanded( flex: 5, child: uploadField, ), ], ); }, ), SizedBox(height: 20), 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 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 _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? empClientId = await tokenService.readValue('empClientId'); final String? empBranchId = await tokenService.readValue('empClientBranchId'); if (token == null || empClientId == null || empBranchId == null) { debugPrint( '❌ Missing required data for navigation $token'); return; } if (!context.mounted) return; await openExcelErrorScreenIfAvailable( context: context, apiService: apiService, fileId: item['id'].toString(), tokenType: 'post', clientId: empClientId, policyNo: item['policy_no']?.toString() ?? '', action: item['file_action']?.toString() ?? '', createdAt: item['created_at']?.toString() ?? '', clientBranchId: empBranchId, 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, ), ), ); } 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 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( tooltip: 'Previous Page', 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 '-'; } } }