import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:nhancepolicy/customAppBar/customAppBar.dart'; import 'dart:convert'; import 'dart:async'; import 'package:http/http.dart' as http; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/hrPolicyDetails.dart'; import 'package:nhancepolicy/service/api_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; import 'package:flutter/foundation.dart' show kIsWeb; import 'dart:io'; import 'package:intl/intl.dart'; import 'package:csv/csv.dart'; import 'package:spreadsheet_decoder/spreadsheet_decoder.dart'; import 'package:url_launcher/url_launcher.dart'; import 'config/environment.dart'; import 'customAppBar/customFooter.dart'; class postFileUpload extends StatefulWidget { final String ClientId; final String policyTypeId; final String ClientPoliyId; final String clientBranchId; final String Token; final String TokenType; final String cardType; final String cardPolicyNo; final String cardInsurer_name; final String cardPolicy_name; final String cardPolicy_ExpDate; const postFileUpload( {Key? key, required this.ClientId, required this.policyTypeId, required this.ClientPoliyId, required this.clientBranchId, required this.Token, required this.TokenType, required this.cardType, required this.cardPolicyNo, required this.cardInsurer_name, required this.cardPolicy_name, required this.cardPolicy_ExpDate}) : super(key: key); @override State createState() => _postFileUploadState(); } class _postFileUploadState extends State { Uint8List? fileBytes; Uint8List? fileBytes2; late String _token; dynamic getPolicyNo; bool _isLoading = false; dynamic getPolicyNameDetails; dynamic clintID; String? fileName; int _currentStep = 0; // Step index tracker List dataPolicy = []; dynamic validationArray = []; dynamic missingColumnErrorMsg = 0; dynamic columnIndexMismatchCount = 0; dynamic columnMissingCount = 0; List> extractedData = []; dynamic argumentsData; List> originalData = []; // Original data source List> filteredData = []; // Filtered data source List> tableData = []; // Filtered data source List> nonExcelFilteredData = []; dynamic invalidRelationships = 0; dynamic dobAgeCheckCount = 0; dynamic empRefId; dynamic empPrimaryId; List> getFileUploadMasterList = []; dynamic getThrFileList = []; late int excelValidationStaus = 1; bool isSuccess = false; String successContent = ''; bool isLoading = false; late ApiService apiService; TextEditingController searchController = TextEditingController(); String? _selectedOption; int _currentPage = 1; int _rowsPerPage = 5; List get _paginatedData { final startIndex = (_currentPage - 1) * _rowsPerPage; final endIndex = (_currentPage * _rowsPerPage).clamp(0, filteredData.length); return filteredData.sublist(startIndex, endIndex); } final List> serviceList = [ {"id": 1, "name": "Sales"}, {"id": 2, "name": "Service"}, ]; String? selectedKey; String? selectedValue; String? _selectedAction; @override void initState() { super.initState(); apiService = ApiService(context); _loadToken(); getFileUploadMasterDetails(); getFileListDetails(); } @override void dispose() { super.dispose(); html.window.localStorage.remove('fileBytes'); } Future _loadToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); // final token = prefs.getString('hrtoken'); final token = widget.Token; if (token != null && token.isNotEmpty) { setState(() { _token = token; }); Map? decodedToken = Jwt.parseJwt(token); print('decodedToken $decodedToken'); } else { // Token is empty or null, handle accordingly (e.g., navigate to login screen) // For now, let's navigate to the login screen ToastHelper.showErrorToast(context, 'Session Out'); Navigator.pushReplacementNamed(context, 'hrLogin'); } } // Future getPolicyDetails() async { // setState(() { // clientPolicyId = argumentsData['client_policy_id']; // clientId = argumentsData['client_id']; // policyType = argumentsData['type']; // policy_name = argumentsData['policy_name']; // }); // } // Future _uploadFile1(importPolicyName) async { // FilePickerResult? result = await FilePicker.platform.pickFiles( // type: FileType.custom, // allowedExtensions: ['xlsx', 'xls', 'csv'], // ); // // if (result != null) { // PlatformFile file = result.files.first; // Uint8List fileBytes = file.bytes!; // // Use the fileBytes as needed // print('File name: ${file.name}'); // print('File size: ${file.size}'); // print('File bytes: $fileBytes'); // _processExcelData(fileBytes); // } else { // // User canceled the picker // } // } Future getFileUploadMasterDetails() async { print('9'); try { final response = await apiService.getFileUploadMastersToApi(widget.Token); if (response['status'] == true) { print('getFileUploadMasterList1'); setState(() { final actions = Map.from(response['data']['actions']); setState(() { getFileUploadMasterList = actions.entries .map((e) => {"key": e.key, "value": e.value}) .toList(); print('getFileUploadMasterList: $getFileUploadMasterList'); }); print('getFileUploadMasterList'); print(getFileUploadMasterList); }); } else { print('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); print('Exception occurred: $e'); } finally { setState(() { // _isLoading = false; }); } } Future getFileListDetails() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); empPrimaryId = prefs.getString('empPrimaryId'); print('9'); try { final response = await apiService.getFileListToApi( empPrimaryId, widget.cardPolicyNo, widget.Token); if (response['status'] == 'success') { print('getThrFileList'); setState(() { getThrFileList = List>.from(response['data']); originalData = getThrFileList; filteredData = List.from(originalData); print('filteredData'); print(filteredData); }); } else { print('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); print('Exception occurred: $e'); } finally { setState(() { // _isLoading = false; }); } } Future getHrFileDownload(id, file_name) async { // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); final apiurl = Environment.apiUrlPost; final String url = '$apiurl/hrFileDownload?id=$id'; final token = widget.Token; final response = await http.get( Uri.parse(url), headers: { 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', // 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); if (response.statusCode == 200) { try { print("PDF Downloaded"); // ✅ Create a blob from the response body bytes final blob = html.Blob([response.bodyBytes]); // ✅ Generate a download URL final url = html.Url.createObjectUrlFromBlob(blob); // ✅ Trigger file download automatically final anchor = html.AnchorElement(href: url) ..setAttribute('download', '$file_name') ..click(); // ✅ Revoke the URL to free memory html.Url.revokeObjectUrl(url); } catch (e) { throw Exception('Error parsing response: $e'); } } else { print("Download failed with status: ${response.statusCode}"); } } void _uploadFile() async { print('Test'); if (kIsWeb) { print('kIsWeb'); final input = html.FileUploadInputElement(); input.accept = '.xlsx,.xls,.csv'; input.click(); input.onChange.listen((event) async { final file = input.files!.first; final reader = html.FileReader(); reader.readAsArrayBuffer(file); await reader.onLoadEnd.first; // Wait for the file to be loaded if (reader.readyState == html.FileReader.DONE) { Uint8List? fileBytes = reader.result as Uint8List?; if (fileBytes != null) { setState(() { fileName = file.name; }); // Save fileBytes to local storage final jsonString = json.encode(fileBytes); html.window.localStorage['fileBytes'] = jsonString; print('File Name: $fileName'); print('File Bytes: $fileBytes'); sendExcelFIleTOAPI(fileBytes, fileName); // Call the function to process Excel data here // _processExcelData(fileBytes, fileName); } } }); } else { // Handle non-web platforms here (e.g., show an error message) print('File upload is only supported on web platforms.'); } } int countInvalidDobs(List> data) { int invalidCount = 0; for (var entry in data) { DateTime dob; // if (entry['DOB'] is String) { dob = DateTime.parse(entry['DOB'].toString()); // } else if (entry['DOB'] is DateTime) { // dob = entry['DOB']; // } else { // // Invalid DOB format, skip this entry // continue; // } print(entry['Relation']); if ((entry['Relation'].toString() == 'Son' || entry['Relation'].toString() == 'Daughter')) { if (DateTime.now().difference(dob).inDays > 25 * 365) { print('child $dob'); invalidCount++; } } else if ((entry['Relation'] != 'Son' && entry['Relation'] != 'Daughter')) { if (DateTime.now().difference(dob).inDays < 18 * 365) { print('others $dob'); invalidCount++; } } } return invalidCount; } void _dragAndDropFile(html.File file) async { print('file'); print(file); // Prepare form data final formData = html.FormData(); formData.appendBlob('file', file); // Send formData to API endpoint final response = await html.HttpRequest.request( 'your_api_endpoint_here', method: 'POST', sendData: formData, ); // Handle response as needed print(response.responseText); } Future sendExcelFIleTOAPI(Uint8List fileBytes, fileName) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); empPrimaryId = prefs.getString('empPrimaryId'); // Future.delayed(Duration(seconds: 3), () { setState(() { isLoading = true; }); // }); print('submit'); print(fileBytes); if (fileBytes == null) { ToastHelper.showErrorToast(context, 'Please upload file'); print('return'); return; // No file selected } else { print('else'); // // Prepare form data // final formData = html.FormData(); // formData.appendBlob('file', html.Blob([fileBytes]), fileName); // URL of the API where you want to send the file final apiUrl = Environment.apiUrlPost + 'hrFileUpload'; print('else'); // Create a multipart request final request = http.MultipartRequest('POST', Uri.parse(apiUrl)); print('else'); // Attach the file to the request // Set authorization token in headers request.headers['APP-SIGNATURE'] = 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y'; request.headers['Authorization'] = 'Bearer $_token'; // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, // filename: fileName)); print('Filename: $fileName'); // request.files.add(http.MultipartFile.fromBytes( // 'file', // fileBytes, // filename: fileName ?? 'default_filename.xlsx', // )); request.files.add(http.MultipartFile.fromBytes( 'file_name', fileBytes, filename: fileName ?? 'default_filename.xlsx', )); print('clintID: $clintID'); request.fields['client_id'] = widget.ClientId; request.fields['policy_no'] = widget.cardPolicyNo; request.fields['client_branch_id'] = widget.clientBranchId; request.fields['file_action'] = selectedKey!; // request.fields['status'] = selectedKey!; request.fields['created_by'] = empPrimaryId; request.fields['policy_id'] = widget.ClientPoliyId; // "client_id": 1, // "client_branch_id": 2, // "policy_no": "POL123456", // "file_action": , // "status": "inception", // "created_by": 10 print('request : $request'); // Send the request final response = await request.send(); print('else'); // Read response stream as a string final responseString = await response.stream.bytesToString(); print(responseString); Map data = json.decode(responseString); if (data['status'] == true) { print('upload success'); setState(() { isLoading = false; }); ToastHelper.showSuccessToast(context, data['message']); getFileListDetails(); setState(() { selectedValue = null; selectedKey = null; resetErrorCount(); }); } else { setState(() { isLoading = false; }); ToastHelper.showErrorToast(context, data['message']); } } } resetErrorCount() { setState(() { fileBytes = null; fileName = null; }); } void search(String query) { print(query); // Check if the query is empty if (query.isEmpty) { // If search query is empty, show all data setState(() { filteredData = List.from(originalData); }); } else { // Filter the original data based on the search query setState(() { filteredData = originalData.where((row) { // Implement your filter logic here // For example, check if any field in the row contains the query // Adjust this logic based on your data structure return row['file_name'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['file_action'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['created_at'] .toString() .toLowerCase() .contains(query.toLowerCase()); }).toList(); }); } print(filteredData.length); } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(), backgroundColor: Color(0xFFEFF3F6), body: Stack(children: [ // color: Color(0xFFEFF3F6), // padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 40), SingleChildScrollView( scrollDirection: Axis.vertical, child: Container( padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50), child: Column( children: [ Container( child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, // 👈 fix alignment children: [ Expanded( flex: 8, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // back button + texts Row( children: [ IconButton( padding: EdgeInsets.zero, constraints: BoxConstraints(), onPressed: () { Navigator.pop(context); }, icon: Icon( Icons.arrow_back_ios, color: Colors.grey, size: 17, ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${widget.cardType} - ${widget.cardPolicyNo}", style: GoogleFonts.poppins( color: Colors.black, fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( widget.TokenType == 'pre' ? "${widget.cardPolicy_name} (${widget.cardPolicy_ExpDate})" : "${widget.cardInsurer_name} - ${widget.cardPolicy_name} (${widget.cardPolicy_ExpDate})", style: GoogleFonts.poppins( color: Colors.grey, fontSize: 12, fontWeight: FontWeight.w400, ), ), ], ) ], ), ], ), ), Expanded( flex: 4, child: Container( padding: const EdgeInsets.only(top: 4), child: getFileUploadMasterList.isEmpty ? const SizedBox() // 👈 avoid crash on first build : buildDropdownField( 'File Action', (value) { setState(() { selectedKey = value; selectedValue = getFileUploadMasterList .firstWhere((item) => item['key'] == value)['value']; }); debugPrint("Selected Key: $selectedKey"); debugPrint( "Selected Value: $selectedValue"); }, false, getFileUploadMasterList, 'value', selectedKey, valueKey: 'key', ), ), ), ], ), ), SizedBox( height: 20, ), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), // 👈 set your desired radius ), // height: 400, // margin: const EdgeInsets.only(left: 40.0, right: 40.0), padding: const EdgeInsets.all(16.0), child: Column( children: [ Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( flex: 12, child: Column( children: [ // Row( // mainAxisAlignment: // MainAxisAlignment.center, // children: [ // Expanded( // child: Text( // '${widget.cardPolicy_name} - (${widget.cardType})', // textAlign: TextAlign.center, // style: const TextStyle( // fontSize: 20, // fontWeight: FontWeight // .w600, // Adjust the font size as needed // color: Color( // 0xFF181818), // Adjust the text color as needed // ), // ), // ) // ], // ), SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Container( margin: EdgeInsets.symmetric( horizontal: 150), alignment: Alignment.center, color: Colors.grey[200], height: 180, child: DragTarget( onAccept: (html.File droppedFile) { setState(() { fileName = droppedFile.name; }); _dragAndDropFile(droppedFile); }, builder: (BuildContext context, List candidateData, List rejectedData) { return Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ fileName != null ? Column( mainAxisAlignment: MainAxisAlignment .center, children: [ Icon( Icons .upload_file, // Choose the appropriate icon size: 35, color: Color( 0xFFE26728), // Adjust the size as needed ), SizedBox( height: 15), // Add some space between the icon and text Text( '$fileName', style: TextStyle( fontSize: 16), ), SizedBox( height: 25), // Add some space between the icon and text MouseRegion( cursor: SystemMouseCursors .click, // Set cursor to pointer on hover child: GestureDetector( onTap: () { resetErrorCount(); }, child: const Row( mainAxisAlignment: MainAxisAlignment .center, children: [ Icon( Icons .delete_forever, // Choose the remove icon size: 20, // Adjust the size as needed color: Colors .red, // Set the color of the remove icon ), SizedBox( width: 1), // Add some space between the icon and text Text( 'Remove', style: TextStyle( fontSize: 13, color: Color(0xFF727272)), ), ], ), ), ) ], ) : ElevatedButton( onPressed: () => { print( 'selectedValue $selectedValue'), if (selectedValue != null) { _uploadFile(), } else { ToastHelper .showErrorToast( context, 'Please select file action') } }, child: Text( 'Select File', style: TextStyle( color: Colors .white), ), style: ElevatedButton .styleFrom( backgroundColor: Color( 0xFFE26728), ), ), SizedBox(height: 20), if (fileName == null) Text( 'Supported Files : XLSX') // Add more Text widgets for additional lines of text ], ), ); }, ), ), ), ], ), ], ), ), ], ), ), ], ), ), SizedBox(height: 10), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), // 👈 set your desired radius ), // width: double.infinity, // take full width // height: MediaQuery.of(context).size.height * 0.8, // 80% of screen height padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ Expanded( flex: 4, child: Align( alignment: Alignment.centerLeft, child: Container( width: 400, height: 37, decoration: BoxDecoration( color: Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(8), ), child: TextField( decoration: InputDecoration( hintText: 'Search', prefixIcon: Icon(Icons.search, size: 18), contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 8), border: InputBorder .none, // No border since Container handles it ), controller: searchController, onChanged: search, style: TextStyle(fontSize: 14), ), ), ), ), ], ), SizedBox(height: 20), Row( children: [ Expanded( child: _buildFileUploadedListTable(context), ) ], ), ], ), ), ], ), ), ), if (isLoading) Container( color: Color(0x98FFFCE5), // Semi-transparent background child: Center( child: // Your GIF loader widget Image.asset( height: 60, width: 60, 'assets/nhance-loader.gif'), // Adjust path to your GIF loader ), ), Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, // Make the footer full width child: CustomFooter(), ), ), ]), ); } Widget buildDropdownField( String label, void Function(T?) onChanged, bool readOnly, List> itemsList, String displayField, T? selectedValue, { String valueKey = 'id', }) { return DropdownButtonFormField( value: selectedValue, decoration: InputDecoration(labelText: label), items: itemsList.map>((item) { return DropdownMenuItem( value: item[valueKey] as T, // ✅ cast to generic type child: Text(item[displayField].toString()), // always display as String ); }).toList(), onChanged: readOnly ? null : onChanged, validator: (value) { if (value == null) { return 'Please select a $label'; } return null; }, ); } Widget _buildFileUploadedListTable(BuildContext context) { // Loader overlay if (filteredData.isEmpty) { return const SizedBox( height: 50, child: Center(child: Text('No available data')), ); } print('filteredData12345 $filteredData'); return ListView.builder( shrinkWrap: true, // important for nested lists physics: const AlwaysScrollableScrollPhysics(), itemCount: _paginatedData.length + 2, itemBuilder: (context, index) { if (index == 0) return _buildHeader(); if (index == _paginatedData.length + 1) return _buildPagination(context); final item = _paginatedData[index - 1]; return _buildDataRow(item); }, ); // return Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // // Header row // Container( // decoration: BoxDecoration( // color: Color(0xFF00A6A6), // borderRadius: BorderRadius.circular(6), // ), // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), // child: Row( // children: [ // Expanded( // flex: 4, // child: Text( // 'Insurer Name', // style: GoogleFonts.poppins( // color: Colors.white, fontWeight: FontWeight.bold), // ), // ), // Expanded( // flex: 3, // child: Text( // 'CD Account number', // style: GoogleFonts.poppins( // color: Colors.white, fontWeight: FontWeight.bold), // ), // ), // Expanded( // flex: 2, // child: Text( // 'Current Balance', // style: GoogleFonts.poppins( // color: Colors.white, fontWeight: FontWeight.bold), // ), // ), // Expanded( // flex: 1, // child: Text( // 'Action', // textAlign: TextAlign.center, // style: GoogleFonts.poppins( // color: Colors.white, fontWeight: FontWeight.bold), // ), // ), // ], // ), // ), // // const SizedBox(height: 6), // // // Table body rows // ..._paginatedData.mapIndexed((index, item) { // return Container( // margin: const EdgeInsets.only(bottom: 8), // padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16), // decoration: BoxDecoration( // color: index % 2 == 0 ? Color(0xFFE6FAFB) : Colors.white, // borderRadius: BorderRadius.circular(6), // ), // child: Row( // children: [ // Expanded( // flex: 4, // child: Text( // item['insurer_name'] ?? '-', // style: GoogleFonts.poppins(color: Color(0xFF000000)), // ), // ), // Expanded( // flex: 3, // child: Text( // item['cd_master_account_no'] ?? '-', // style: GoogleFonts.poppins(color: Color(0xFF000000)), // ), // ), // Expanded( // flex: 2, // child: Text( // "₹${item['balance'] ?? '0'}", // style: GoogleFonts.poppins(color: Color(0xFF000000)), // ), // ), // Expanded( // flex: 1, // child: Center( // child: IconButton( // icon: const Icon(Icons.remove_red_eye_outlined), // onPressed: () { // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => cdTransactionDetails( // insurerName: item['insurer_name'], // cdMasterAccountNo: item['cd_master_account_no'], // insurerId: item['insurer_id'], // cd_ac_pk: item['cd_ac_pk'], // empClientId: widget.empClientId, // postToken: widget.postToken), // ), // ); // }, // ), // ), // ), // ], // ), // ); // }).toList(), // // // ], // ); } Widget _buildDataRow(Map item) { print('item $item'); return Container( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), margin: const EdgeInsets.only(top: 10), decoration: BoxDecoration( color: Color(0xFFE0F7F9), border: const Border( bottom: BorderSide(color: Color(0xFFD7E9EB), width: 1), ), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( flex: 4, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MouseRegion( cursor: SystemMouseCursors.click, // 👆 Pointer cursor child: Tooltip( message: "Click to Download", // 📝 Tooltip text child: GestureDetector( onTap: () { getHrFileDownload( item['id'], item[ 'file_name']); // ✅ Call your API/download logic }, child: Text( item['file_name'] ?? '-', style: _dataBold.copyWith( color: Colors.blue, // 🔗 Make it look like a link decoration: TextDecoration.underline, ), ), ), ), ), ], ), ), Expanded( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item['file_action'] ?? '-', style: _dataBold), ], ), ), Expanded( flex: 2, child: Text(formatDate(item['created_at']), style: _dataBold), ), Expanded( flex: 2, child: Text(item['status'], style: _dataBold), ), ], ), ); } Widget _buildHeader() { return Container( decoration: BoxDecoration( color: Color(0xFF00A6A6), borderRadius: BorderRadius.circular(6), ), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: const Row( children: [ Expanded(flex: 4, child: Text('File Name', style: _headerStyle)), Expanded(flex: 3, child: Text('File Action', style: _headerStyle)), Expanded(flex: 2, child: Text('Created At', style: _headerStyle)), Expanded(flex: 2, child: Text('Status', style: _headerStyle)) ], ), ); } static final _dataBold = GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); static final _dataSub = GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); static const _headerStyle = TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ); Widget _buildPagination(BuildContext context) { final totalPages = (filteredData.length / _rowsPerPage).ceil(); const visiblePageCount = 5; List getVisiblePages() { if (totalPages <= visiblePageCount) { return List.generate(totalPages, (i) => i + 1); } if (_currentPage <= 3) { return [1, 2, 3, 4, 5]; } else if (_currentPage >= totalPages - 2) { return [ totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages ]; } else { return [ _currentPage - 2, _currentPage - 1, _currentPage, _currentPage + 1, _currentPage + 2, ]; } } List visiblePages = getVisiblePages(); return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( children: [ // Dropdown for rows per page DropdownButton( value: _rowsPerPage, items: [5, 10, 15, 20, 50].map((int value) { return DropdownMenuItem( value: value, child: Text(' $value ', style: GoogleFonts.poppins(fontSize: 15)), ); }).toList(), onChanged: (newValue) { setState(() { _rowsPerPage = newValue!; _currentPage = 1; }); }, ), // Previous button IconButton( onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, icon: const Icon(Icons.chevron_left), ), // First page + left ellipsis if (!visiblePages.contains(1)) Row(children: [ _buildPageButton(1), const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text("..."), ), ]), // Visible page buttons for (int page in visiblePages) _buildPageButton(page), // Right ellipsis + last page if (!visiblePages.contains(totalPages)) Row(children: [ const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text("..."), ), _buildPageButton(totalPages), ]), // Next button IconButton( onPressed: _currentPage < totalPages ? () => setState(() => _currentPage++) : null, icon: const Icon(Icons.chevron_right), ), ], ), ), ], ); } Widget _buildPageButton(int page) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: _currentPage == page ? const Color(0xFF00A6A6) : Colors.grey[300], foregroundColor: _currentPage == page ? Colors.white : Colors.black, minimumSize: const Size(36, 36), padding: EdgeInsets.zero, ), onPressed: () { setState(() { _currentPage = page; }); }, child: Text(page.toString()), ), ); } String formatDate(String? dateString) { if (dateString == null || dateString.isEmpty) return '-'; try { DateTime parsedDate = DateTime.parse(dateString); return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); } catch (e) { return '-'; } } }