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'; } } }