import 'dart:convert'; import 'package:csv/csv.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; 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:url_launcher/url_launcher.dart'; import '../config/environment.dart'; import '../customAppBar/base_layout.dart'; import 'cdList.dart'; import 'claims.dart'; import 'package:nhancepolicy/logger.dart'; class cdTransactionDetails extends StatefulWidget { final String insurerName; final String cdMasterAccountNo; final String insurerId; final String cd_ac_pk; final String empClientId; // final String postToken; const cdTransactionDetails({ Key? key, required this.insurerName, required this.cdMasterAccountNo, required this.insurerId, required this.cd_ac_pk, required this.empClientId, // required this.postToken }); @override State createState() => _cdTransactionDetailsState(); } class _cdTransactionDetailsState extends State { String? localInsurerId; String? localCdAcPk; String? localEmpClientId; String? localInsurerName; String? localCdMasterAccountNo; final tokenService = TokenStorageService(); Uint8List? fileBytes; List> getCDTransData = []; List> getCDEndorsementData = []; bool isLoading = false; bool _isLoading = false; // dynamic clintID; late TabController _tabController; // List dataPolicy = []; List reversedDataPolicy = []; List> originalData = []; // Original data source List> originalEndorsementData = []; // Original data source List> filteredData = []; // Filtered data source List> filteredEndorsementData = []; // Filtered data source List> getCDTransDataAmount = []; // Filtered data source dynamic argumentsData; dynamic policyType; dynamic policyName; dynamic clientPolicyId; dynamic clientId; dynamic empRefId; dynamic total_deposit; dynamic total_consumed; dynamic total_refund; dynamic currect_balance; dynamic insurer_short_name; int inceptionType = 0; TextEditingController searchController = TextEditingController(); late ApiService apiService; int _currentPage = 1; int _rowsPerPage = 5; List get _paginatedData { final startIndex = (_currentPage - 1) * _rowsPerPage; final endIndex = (_currentPage * _rowsPerPage).clamp(0, filteredData.length); return filteredData.sublist(startIndex, endIndex); } @override void initState() { super.initState(); apiService = ApiService(context); // Initialize ApiService here restoreTransactionData(); } @override void dispose() { super.dispose(); } // downloadPolicyFiles?file_id=13 // getPolicyAndEndorsementFiles?cd_ac_pk=12 Future restoreTransactionData() async { localInsurerId = widget.insurerId.trim().isNotEmpty ? widget.insurerId : await tokenService.readValue('hr_cd_insurer_id'); localCdAcPk = widget.cd_ac_pk.trim().isNotEmpty ? widget.cd_ac_pk : await tokenService.readValue('hr_cd_ac_pk'); localEmpClientId = widget.empClientId.trim().isNotEmpty ? widget.empClientId : await tokenService.readValue('hr_empClientId'); localInsurerName = widget.insurerName.trim().isNotEmpty ? widget.insurerName : await tokenService.readValue('hr_cd_insurer_name'); localCdMasterAccountNo = widget.cdMasterAccountNo.trim().isNotEmpty ? widget.cdMasterAccountNo : await tokenService.readValue('hr_cd_master_account_no'); logDebug('restore localInsurerId = $localInsurerId'); logDebug('restore localCdAcPk = $localCdAcPk'); logDebug('restore localEmpClientId = $localEmpClientId'); if (localInsurerId != null && localCdAcPk != null && localEmpClientId != null) { getCdTransactionDetails(); } } Future clearPolicyStorage() async { await tokenService.removeValue('hr_cd_insurer_id'); await tokenService.removeValue('hr_cd_ac_pk'); await tokenService.removeValue('hr_empClientId'); await tokenService.removeValue('hr_cd_insurer_name'); await tokenService.removeValue('hr_cd_master_account_no'); } Future getCdTransactionDetails() async { logDebug('9'); setState(() { isLoading = true; }); try { logDebug('10'); final _postPreToken = await tokenService.getCurrentToken(); final response = await apiService.getCdTransactionData( localEmpClientId!, localInsurerId!, localCdAcPk!, _postPreToken!); if (response['status'] == 'success') { setState(() { isLoading = false; }); setState(() { getCDTransData = List>.from(response['data']['deposit_data']); originalData = getCDTransData; filteredData = List.from(originalData); total_deposit = response['data']['total_deposit']; // total_deposit = formatAmount(response['data']['total_deposit']); total_consumed = response['data']['total_consumed']; // total_consumed = formatAmount(response['data']['total_consumed']); total_refund = response['data']['total_refund']; // total_refund = formatAmount(response['data']['total_refund']); currect_balance = response['data']['currect_balance']; // currect_balance = formatAmount(response['data']['currect_balance']); insurer_short_name = response['data']['insurer_short_name']; // insurer_short_name = formatAmount(response['data']['insurer_short_name']); logDebug('filteredData'); logDebug(filteredData); }); } else { setState(() { isLoading = false; }); // ToastHelper.showWarningToast( // context, 'Request failed with status: ${response.statusCode}'); logDebug('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); logDebug('Exception occurred: $e'); } finally { setState(() { _isLoading = false; }); } } Future _openEndorsementFile(id, file_name) async { // final http.Response response = await apiService.getHrFileDownloadToApi(id, widget.Token); final _postPreToken = await tokenService.getCurrentToken(); logDebug("**********-------*****"); final apiurl = Environment.apiUrlPost; final String url = '$apiurl/downloadPolicyFiles?file_id=$id'; final response = await http.get( Uri.parse(url), headers: { 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', 'Authorization': 'Bearer $_postPreToken', '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 getCdEndorsementDetails(id) async { logDebug('9'); setState(() { isLoading = true; }); try { logDebug('10'); final _postPreToken = await tokenService.getCurrentToken(); final response = await apiService.getCdEndorsementData(id, _postPreToken!); if (response['status'] == true) { setState(() { isLoading = false; }); setState(() { getCDEndorsementData = List>.from(response['data']); originalEndorsementData = getCDEndorsementData; filteredEndorsementData = List.from(originalEndorsementData); _showFileListPopup(filteredEndorsementData); logDebug('filteredData'); logDebug(filteredData); }); } else { setState(() { isLoading = false; }); getCDEndorsementData = List>.from(response['data']); if (getCDEndorsementData == null || getCDEndorsementData.isEmpty || getCDEndorsementData == null || (getCDEndorsementData as List).isEmpty) { _showEmptyPopup(response['message']); return; } // ToastHelper.showWarningToast( // context, 'Request failed with status: ${response.statusCode}'); logDebug('Request failed with status: ${response['code']}'); } } catch (e) { setState(() { isLoading = false; }); logDebug('Exception occurred: $e'); } finally { setState(() { _isLoading = false; }); } } void _showFileListPopup(List> files) { showDialog( context: context, barrierDismissible: true, builder: (_) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Text( 'Files', style: TextStyle(fontWeight: FontWeight.w600), ), content: SizedBox( width: 400, child: ListView.separated( shrinkWrap: true, itemCount: files.length, separatorBuilder: (_, __) => const Divider(), itemBuilder: (context, index) { final file = files[index]; return ListTile( leading: const Icon( Icons.picture_as_pdf_outlined, color: Color(0xFF00999E), ), title: Text( file['file_name'] ?? 'Document', style: const TextStyle(fontSize: 14), ), onTap: () { // Navigator.pop(context); // close popup _openEndorsementFile(file['id'], file['file_name']); }, ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Close'), ), ], ); }, ); } void _showEmptyPopup(String message) { showDialog( context: context, barrierDismissible: false, builder: (_) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Text( 'Message', style: TextStyle(fontWeight: FontWeight.w600), ), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('OK'), ), ], ); }, ); } void search(String query) { 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['created_at'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['record_date'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['unit'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['policy_type'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['policy_no'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['endorsement_no'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['sub_type'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['transaction_type'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['amount'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['balance'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['description'] .toString() .toLowerCase() .contains(query.toLowerCase()) || row['username'] .toString() .toLowerCase() .contains(query.toLowerCase()); }).toList(); }); } logDebug(filteredData.length); } void exportToCsv(List> data) { List> rows = []; // Header rows.add([ 'Date', 'Record Date', 'Unit', 'Policy', 'Endorsement No', 'Sub Type', 'Credit', 'Debit', 'Balance', 'Description', 'User' ]); // Data rows for (var item in data) { rows.add([ item['created_at'] ?? '', item['record_date'] ?? '', item['unit'] ?? '', item['policy_type'] != null && item['policy_type'].toString().trim().isNotEmpty && item['policy_no'] != null && item['policy_no'].toString().trim().isNotEmpty ? '${item['policy_type']} - ${item['policy_no']}' : '-', item['endorsement_no'] ?? '', item['sub_type_text'] ?? '', item['transaction_type'] == 'Credit' ? '${item['amount']}' : '-', // item['transaction_type'] == 'Credit' ? '₹${formatAmount(item['amount'])}' : '-', item['transaction_type'] == 'Debit' ? '${item['amount']}' : '-', // item['transaction_type'] == 'Debit' ? '₹${formatAmount(item['amount'])}' : '-', '${item['balance'] ?? '0'}', // '₹${formatAmount(item['balance']) ?? '0'}', item['description'] ?? '', item['username'] ?? '', ]); } // Convert to CSV string String csvData = const ListToCsvConverter().convert(rows); // For Web: Create download final bytes = utf8.encode(csvData); final blob = html.Blob([bytes]); final url = html.Url.createObjectUrlFromBlob(blob); final anchor = html.AnchorElement(href: url) ..setAttribute("download", "CD_Transaction_Policies.csv") ..click(); html.Url.revokeObjectUrl(url); handleExportAction(); } Future handleExportAction() async { logDebug('handleExportAction'); final postId = await tokenService.readValue('empHrId'); final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); var activity = "export_cdsummary"; logDebug('postId - $postId'); logDebug('preId - $preId'); logDebug('activity - $activity'); try { logDebug('10'); final _postPreToken = await tokenService.getCurrentToken(); final response = await apiService.getPostLogHrActivity( postId!, preId!, _postPreToken!, activity); if (response['status'] == 'success') { logDebug('Request success'); } else { // ToastHelper.showWarningToast( // context, 'Request failed with status: ${response.statusCode}'); logDebug('Request failed with status: ${response['code']}'); } } catch (e) { logDebug('Exception occurred: $e'); } } String formatDate(String? dateString) { if (dateString == null || dateString.isEmpty) return '-'; try { DateTime parsedDate = DateTime.parse(dateString); return DateFormat('dd-MM-yyyy').format(parsedDate); } catch (e) { return '-'; } } String formatDateNextLine(String? dateString) { if (dateString == null || dateString.isEmpty) return '-'; try { DateTime parsedDate = DateTime.parse(dateString); String date = DateFormat('dd-MM-yyyy').format(parsedDate); String time = DateFormat('hh:mm a').format(parsedDate); return '$date\n$time'; } catch (e) { return '-'; } } Future _launchURL(String url) async { final Uri uri = Uri.parse(url); // Parse the URL properly logDebug('_launchURL $uri'); if (uri != null) { logDebug('If $uri'); await launchUrl(uri, mode: LaunchMode.externalApplication); } else { ToastHelper.showWarningToast(context, 'File not generated'); logDebug('else $uri'); throw 'Could not launch $url'; } } Future _showLogoutDialog() async { return await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text("Confirm Logout"), content: Text("Do you want to logout?"), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text("Cancel"), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text("Logout"), ), ], ), ) ?? false; } String formatAmount(dynamic value) { if (value == null) return '-'; final num amount = num.tryParse(value.toString()) ?? 0; return amount.round().toString(); } @override Widget build(BuildContext context) { return BaseLayout( child: PopScope( canPop: false, // 🚫 block default back onPopInvoked: (didPop) async { if (didPop) return; Navigator.pop(context); // 👈 go to previous page }, 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( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ /// 🔙 Back + Title (LEFT) Row( children: [ IconButton( tooltip: 'Previous Page', onPressed: () async { await clearPolicyStorage(); if (Navigator.canPop(context)) { Navigator.pop(context); } else { Navigator.pushReplacement( context, MaterialPageRoute( settings: const RouteSettings(name: 'cdPoliciesList'), builder: (_) => CdPoliciesList(), ), ); } }, icon: const Icon( Icons.arrow_back_ios, size: 18, color: Colors.black, ), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), const SizedBox(width: 6), Text( 'Transaction Details - ${insurer_short_name} (${localCdMasterAccountNo ?? widget.cdMasterAccountNo})', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.black, ), ), ], ), /// Push right content to end const Spacer(), /// 🔍 Search Box Container( width: 380, height: 37, decoration: BoxDecoration( color: const Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(8), ), child: TextField( controller: searchController, onChanged: search, style: GoogleFonts.poppins(fontSize: 14), decoration: const InputDecoration( hintText: 'Search', prefixIcon: Icon(Icons.search, size: 18), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), ), const SizedBox(width: 12), /// ⬇️ Export Button SizedBox( width: 116, height: 37, child: ElevatedButton( onPressed: () => exportToCsv(filteredData), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE26728), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: Text( 'Export', style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, letterSpacing: 1, ), ), ), ), ], ), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildInfoCard('Deposits', '₹$total_deposit', Color(0xFF39D45B), Icons.arrow_upward), _buildInfoCard('Consumed', '₹$total_consumed', Color(0xFFED1D24), Icons.arrow_downward), _buildInfoCard('Refund', '₹$total_refund', Color(0xFF39D45B), Icons.arrow_upward), _buildInfoCard('Current Balance', '₹$currect_balance', Colors.black, null), ], ), SizedBox(height: 16), Expanded( child: _buildCDDataTable(context), ), ], )); } Widget _buildInfoCard( String label, String value, Color iconColor, IconData? icon) { return Expanded( child: Container( margin: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 2, offset: Offset(0, 1), ), ], ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // label left, value right children: [ Text( label, style: GoogleFonts.poppins( color: Color(0xFF737373), fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.left, ), Row( children: [ if (icon != null) Icon(icon, size: 20, color: iconColor), if (icon != null) SizedBox(width: 4), Text( value, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Color(0xFF000000), ), ), ], ), ], ), ], ), ), ); } Widget _buildCDDataTable(BuildContext context) { if (filteredData.isEmpty) { return const Center(child: Text('No available data')); } return CustomScrollView( slivers: [ /// 🔒 FIXED HEADER SliverPersistentHeader( pinned: true, delegate: _CDTableHeaderDelegate(), ), /// 📄 TABLE ROWS SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final item = _paginatedData[index]; return _buildCDRow(item); }, childCount: _paginatedData.length, ), ), /// 📌 PAGINATION SliverToBoxAdapter( child: _buildPagination(context), ), ], ); } Widget _buildCDRow(Map item) { final bool isAllowedSubType = item['sub_type'] == '3' || item['sub_type'] == '4'; final bool hasSplitUpFile = item['split_up_url'] != null && item['split_up_url'].toString().trim().isNotEmpty; return Container( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: Color(0xFFA9D9DE), width: 1), ), ), child: Row( children: [ _cell(formatDateNextLine(item['created_at']), 2), SizedBox(width: 10), _cell(formatDate(item['record_date']), 2), SizedBox(width: 10), _cell(item['unit'], 2), SizedBox(width: 10), _cell( item['policy_type'] != null && item['policy_no'] != null ? '${item['policy_type']} - ${item['policy_no']}' : '-', 3, ), SizedBox(width: 10), _cell(item['endorsement_no'], 3), SizedBox(width: 10), _cell(item['sub_type_text'], 2), SizedBox(width: 10), _cell( item['transaction_type'] == 'Credit' ? item['amount'] : '-', 2, alignRight: true, ), // _cell( // item['transaction_type'] == 'Credit' // ? formatAmount(item['amount']) // : '-', // 2, // alignRight: true, // ), SizedBox(width: 10), _cell( item['transaction_type'] == 'Debit' ? item['amount'] : '-', 2, alignRight: true, ), // _cell( // item['transaction_type'] == 'Debit' // ? formatAmount(item['amount']) // : '-', // 2, // alignRight: true, // ), SizedBox(width: 10), _cell(item['balance'], 2, alignRight: true), // _cell(formatAmount(item['balance']), 2, alignRight: true), SizedBox(width: 10), _cell(item['description'], 3), SizedBox(width: 10), _cell(item['username'], 2), SizedBox(width: 10), Expanded( flex: 2, child: SizedBox( height: 36, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ /// --- PDF ICON SLOT --- SizedBox( width: 36, height: 36, child: Visibility( visible: isAllowedSubType, maintainSize: true, maintainAnimation: true, maintainState: true, child: _ActionIconButton( icon: Icons.picture_as_pdf_outlined, toolTip: 'View Endorsement PDF', onTap: () => getCdEndorsementDetails(item['id']), ), ), ), const SizedBox(width: 8), /// --- FOLDER ICON SLOT --- SizedBox( width: 36, height: 36, child: Visibility( visible: isAllowedSubType && hasSplitUpFile, maintainSize: true, maintainAnimation: true, maintainState: true, child: _ActionIconButton( icon: Icons.folder_open_outlined, toolTip: 'View Files', onTap: () => _launchURL(item['split_up_url']), ), ), ), ], ), ), ), // Expanded( // flex: 2, // child: Row( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // if (isAllowedSubType) // _ActionIconButton( // icon: Icons.picture_as_pdf_outlined, // toolTip: 'View Endorsement PDF', // onTap: () => getCdEndorsementDetails(item['id']), // ), // const SizedBox(width: 8), // if (isAllowedSubType && hasSplitUpFile) // _ActionIconButton( // icon: Icons.folder_open_outlined, // toolTip: 'View Files', // onTap: () => _launchURL(item['split_up_url']), // ), // ], // ), // ), ], ), ); } Widget _cell(String? text, int flex, {bool alignRight = false}) { return Expanded( flex: flex, child: Text( text ?? '-', textAlign: alignRight ? TextAlign.right : TextAlign.left, style: GoogleFonts.poppins(fontSize: 12), // overflow: TextOverflow.ellipsis, ), ); } Widget _buildPagination(BuildContext context) { final totalItems = filteredData.length; // Calculate entries range final int startEntry = totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1; int endEntry = _currentPage * _rowsPerPage; if (endEntry > totalItems) endEntry = totalItems; // Calculate total pages final int totalPages = (totalItems / _rowsPerPage).ceil(); const int visiblePageCount = 5; // Helper logic for page numbers 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(); return Padding( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // Standard Table Footer Layout children: [ /// --- LEFT SIDE: ENTRY DETAILS --- Text( 'Showing $startEntry to $endEntry of $totalItems entries', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, color: const Color(0xFF666666), ), ), /// --- RIGHT SIDE: CONTROLS --- Row( children: [ 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; }); }, ), const SizedBox(width: 16), IconButton( onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, icon: const Icon(Icons.chevron_left), ), if (!visiblePages.contains(1)) Row(children: [ _buildPageButton(1), const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text("..."), ), ]), // Visible page buttons 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), ]), // Next button IconButton( onPressed: _currentPage < totalPages ? () => setState(() => _currentPage++) : null, icon: const Icon(Icons.chevron_right), ), ], ), ], ), ); } Widget _buildPagination_backup(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()), ), ); } } class _CDTableHeaderDelegate extends SliverPersistentHeaderDelegate { @override double get minExtent => 52; @override double get maxExtent => 52; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return Container( decoration: BoxDecoration( color: Color(0xFFD7E9EB), borderRadius: BorderRadius.circular(6), ), // color: Color(0xFFD7E9EB), padding: EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.centerLeft, child: const Row( children: [ _HeaderCell('Date', 2), SizedBox(width: 10), _HeaderCell('Record Date', 2), SizedBox(width: 10), _HeaderCell('Unit', 2), SizedBox(width: 10), _HeaderCell('Policy', 3), SizedBox(width: 10), _HeaderCell('Endorsement No', 3), SizedBox(width: 10), _HeaderCell('Sub Type', 2), SizedBox(width: 10), _HeaderCell('₹ Credit', 2, alignRight: true), SizedBox(width: 10), _HeaderCell('₹ Debit', 2, alignRight: true), SizedBox(width: 10), _HeaderCell('₹ Balance', 2, alignRight: true), SizedBox(width: 10), _HeaderCell('Description', 3), SizedBox(width: 10), _HeaderCell('User', 2), SizedBox(width: 10), _HeaderCell('Action', 2, center: true), SizedBox(width: 10), ], ), ); } @override bool shouldRebuild(_) => false; } class _HeaderCell extends StatelessWidget { final String text; final int flex; final bool alignRight; final bool center; const _HeaderCell( this.text, this.flex, { this.alignRight = false, this.center = false, }); @override Widget build(BuildContext context) { return Expanded( flex: flex, child: Text( text, textAlign: center ? TextAlign.center : (alignRight ? TextAlign.right : TextAlign.left), style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: const Color(0xFF000000), ), ), ); } } class _ActionIconButton extends StatelessWidget { final IconData icon; final VoidCallback onTap; final bool enabled; final String? toolTip; // 1. Define the optional tooltip string const _ActionIconButton({ required this.icon, required this.onTap, this.toolTip, // 2. Added to constructor this.enabled = true, }); @override Widget build(BuildContext context) { // 3. Define the main button widget Widget button = SizedBox( width: 36, height: 36, child: Material( color: enabled ? const Color(0xFFDFF4F5) : Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( borderRadius: BorderRadius.circular(10), onTap: enabled ? onTap : null, child: Icon( icon, size: 22, color: enabled ? Colors.black : Colors.transparent, ), ), ), ); // 4. Wrap with Tooltip only if enabled and tooltip text exists if (enabled && toolTip != null) { return Tooltip( message: toolTip!, preferBelow: false, // Shows tooltip above the button child: button, ); } return button; } } // Sample Data class representing each element in the array class Data { final dynamic value; final int row; final int column; final String sheet; Data(this.value, this.row, this.column, this.sheet); }