import 'dart:ui'; import 'package:csv/csv.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:nhancepolicy/presentation/preFileUpload.dart'; import 'package:nhancepolicy/presentation/postFileUpload.dart'; import 'package:nhancepolicy/service/api_service.dart'; import 'dart:convert'; import 'dart:async'; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:universal_html/html.dart' as html; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:url_launcher/url_launcher.dart'; import '../customAppBar/base_layout.dart'; import 'package:nhancepolicy/logger.dart'; class excelErrorScreen extends StatefulWidget { final String ClientId; final String policy_no; final String action; final String created_at; final String clientBranchId; final String Token; final String TokenType; final String id; const excelErrorScreen({ Key? key, required this.ClientId, required this.policy_no, required this.action, required this.created_at, required this.clientBranchId, required this.Token, required this.TokenType, required this.id, }) : super(key: key); @override State createState() => _activePolicyExcelErrorState(); } class _activePolicyExcelErrorState extends State with TickerProviderStateMixin { final tokenService = TokenStorageService(); bool isLoading = false; dynamic empPrimaryId; dynamic empClientId; dynamic empClientBranchId; dynamic empHrId; dynamic enrollmentClient_id; dynamic enrollmentEmpClientBranchId; dynamic enrollmentHrId; TextEditingController searchController = TextEditingController(); List excelHeader = []; List>> excelData = []; late int excelValidationStaus = 1; bool isSuccess = false; String successContent = ''; List>> filteredExcelData = []; String? _postPreToken = ''; late ApiService apiService; int _currentPage = 1; int _rowsPerPage = 6; List>> get _paginatedExcelData { final start = (_currentPage - 1) * _rowsPerPage; final end = (_currentPage * _rowsPerPage).clamp(0, filteredExcelData.length); return filteredExcelData.sublist(start, end); } final ScrollController _verticalController = ScrollController(); final ScrollController _horizontalController = ScrollController(); @override void initState() { super.initState(); apiService = ApiService(context); getCDPoliciesDetails(); } @override void dispose() { _verticalController.dispose(); _horizontalController.dispose(); super.dispose(); } Future getCDPoliciesDetails() async { setState(() { isLoading = true; }); try { final response = await apiService.getExcelFileErrorsApi(widget.id, widget.TokenType); if (!hasExcelErrorTableData(response)) { if (!mounted) return; setState(() => isLoading = false); _showEmptyDataDialog( response['message']?.toString() ?? 'No error data available', ); return; } if (response['message'] == 'Error data feteched successfully') { ToastHelper.showErrorToast(context, response['message']); } else { ToastHelper.showSuccessToast(context, response['message']); } if (!mounted) return; setState(() { isLoading = false; isSuccess = false; excelValidationStaus = 1; excelHeader = List.from(response['data']['excel_header'] as List); excelData = (response['data']['excel_data'] as List) .map>>( (row) => row .map>( (cell) => Map.from(cell as Map), ) .toList(), ) .toList(); filteredExcelData = List.from(excelData); _currentPage = 1; }); } catch (e) { if (!mounted) return; setState(() => isLoading = false); logDebug('Exception occurred: $e'); _showEmptyDataDialog('Something went wrong. Please try again.'); } } void _showEmptyDataDialog(String message) { showExcelErrorMessageDialog( context, message, popRouteOnClose: true, ); } void search(String query) { if (query.isEmpty) { setState(() { filteredExcelData = List.from(excelData); _currentPage = 1; }); return; } final lowerQuery = query.toLowerCase(); setState(() { filteredExcelData = excelData.where((row) { return row.any((cell) { final value = cell['value']; return value != null && value.toString().toLowerCase().contains(lowerQuery); }); }).toList(); _currentPage = 1; }); } // emp_is_active void exportToCsv({ required List excelHeader, required List>> excelData, }) { List> rows = []; /// 1️⃣ Add headers rows.add(excelHeader); /// 2️⃣ Add rows for (final row in excelData) { rows.add( row.map((cell) { final value = cell['value']; return value == null ? '' : value.toString(); }).toList(), ); } /// 3️⃣ Convert to CSV final csvData = const ListToCsvConverter().convert(rows); /// 4️⃣ Download (Flutter Web) final bytes = utf8.encode(csvData); final blob = html.Blob([bytes], 'text/csv'); final url = html.Url.createObjectUrlFromBlob(blob); html.AnchorElement(href: url) ..setAttribute("download", "Excel_Error_File.csv") ..click(); html.Url.revokeObjectUrl(url); } // Future handleExportAction() async { // logDebug('handleExportAction'); // // final postId = await tokenService.readValue('empHrId'); // final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); // // var activity = "export_empdata"; // // var activityPre = "export_preempdata"; // dynamic response; // // logDebug('postId - $postId'); // logDebug('preId - $preId'); // logDebug('activity - $activity'); // // try { // logDebug('10'); // // if (widget.TokenType == 'pre') { // response = await apiService.getPreLogHrActivity( // postId!, preId!, widget.Token, activityPre); // } else if (widget.TokenType == 'post') { // response = await apiService.getPostLogHrActivity( // postId!, preId!, widget.Token, 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 _capitalize(String? value) { if (value == null || value.isEmpty) return ''; return value[0].toUpperCase() + value.substring(1).toLowerCase(); } String formatDateTime(String dateTime) { final parsedDate = DateTime.parse(dateTime); return DateFormat('dd-MM-yyyy hh:mm a').format(parsedDate); } @override Widget build(BuildContext context) { return BaseLayout( child: _buildContent(context), ); } Widget _buildContent(BuildContext context) { if (isLoading || excelHeader.isEmpty) { return Container( color: const Color(0x98FFFCE5), child: Center( child: Image.asset( height: 60, width: 60, 'assets/nhance-loader.gif', ), ), ); } return Container( // padding: EdgeInsets.only(top: 30, bottom: 200, left: 50, right: 50), child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ /// 🔙 Back + Title (LEFT) 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( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.policy_no ?? '', style: GoogleFonts.poppins( color: Colors.black, fontSize: 14, fontWeight: FontWeight.w500, ), ), if (widget.TokenType != 'pre') RichText( text: TextSpan( style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, ), children: [ TextSpan( text: widget.action, style: const TextStyle( color: Color(0xFF00999E), ), ), const TextSpan( text: ' - ', style: TextStyle( color: Color(0xFF585858), ), ), TextSpan( text: formatDateTime(widget.created_at), style: const TextStyle( color: Color(0xFF585858), ), ), ], ), ), ], ), ), ], ), /// 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( excelHeader: excelHeader, excelData: filteredExcelData, // or excelData ); }, 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: 20), Expanded( child: _buildCDDataTable(context), ), ], ), ); } Widget _buildCDDataTable(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ScrollConfiguration( behavior: const MaterialScrollBehavior().copyWith( dragDevices: { PointerDeviceKind.mouse, PointerDeviceKind.touch, PointerDeviceKind.trackpad, }, ), child: _buildScrollableTable(context), ), ), _buildPagination(context), ], ); } static const Map _staticColumnWidthByHeader = { 'sno': 56, 'emp code': 128, 'name': 140, 'doj': 128, 'gender': 72, 'relation': 140, 'relationship': 140, 'dob': 152, 'mail': 220, 'mobile': 128, 'si': 96, 'grade': 96, 'basic pay': 100, 'unit': 80, 'doc': 128, }; double _staticColumnWidth(String header) { return _staticColumnWidthByHeader[header.trim().toLowerCase()] ?? 120; } Map get _tableColumnWidths { return { for (var i = 0; i < excelHeader.length; i++) i: FixedColumnWidth(_staticColumnWidth(excelHeader[i])), }; } double get _totalTableWidth { return excelHeader.fold( 0, (sum, header) => sum + _staticColumnWidth(header), ); } Widget _ellipsizedText( String value, { TextStyle? style, }) { final text = Text( value, style: style, maxLines: 1, softWrap: false, overflow: TextOverflow.ellipsis, ); if (value.isEmpty || value == '-') { return text; } return Tooltip( message: value, waitDuration: const Duration(milliseconds: 300), child: text, ); } Widget _tableHeaderCell(String header) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: _ellipsizedText( header, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, ), ), ); } Widget _tableDataCell(Map cell) { final value = cell['value']?.toString() ?? '-'; final textStyle = GoogleFonts.poppins(fontSize: 12); final hasError = cell.containsKey('error'); if (!hasError) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: _ellipsizedText(value, style: textStyle), ); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( children: [ Expanded( child: _ellipsizedText(value, style: textStyle), ), const SizedBox(width: 4), IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 24, minHeight: 24), icon: const Icon( Icons.error_outline, color: Colors.red, size: 16, ), onPressed: () => _showCellErrorDialog(cell['error'] as List), ), ], ), ); } void _showCellErrorDialog(List errors) { showDialog( context: context, barrierDismissible: true, builder: (_) { return Dialog( backgroundColor: Colors.transparent, child: Container( width: 420, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Error!', style: GoogleFonts.poppins( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black, ), ), const SizedBox(height: 16), Container( width: 64, height: 64, decoration: const BoxDecoration( color: Color(0xFFE0002A), shape: BoxShape.circle, ), child: const Center( child: Text( '!', style: TextStyle( color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(height: 20), Text( errors.isNotEmpty ? errors.first.toString() : 'Validation Error', textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), ), const SizedBox(height: 12), ...errors.skip(1).map( (e) => Padding( padding: const EdgeInsets.only(top: 6), child: Text( e.toString(), textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 14, color: const Color(0xFFE09B2D), ), ), ), ), const SizedBox(height: 20), SizedBox( width: 120, height: 30, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE0002A), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), elevation: 0, ), child: Text( 'OK', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ], ), ), ); }, ); } Widget _buildScrollableTable(BuildContext context) { final tableWidth = _totalTableWidth; return Scrollbar( thumbVisibility: true, controller: _verticalController, child: SingleChildScrollView( controller: _verticalController, physics: const ClampingScrollPhysics(), scrollDirection: Axis.vertical, child: Scrollbar( thumbVisibility: true, controller: _horizontalController, notificationPredicate: (n) => n.depth == 1, child: SingleChildScrollView( controller: _horizontalController, physics: const ClampingScrollPhysics(), scrollDirection: Axis.horizontal, child: ConstrainedBox( constraints: BoxConstraints.tightFor(width: tableWidth), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ ClipRRect( borderRadius: BorderRadius.circular(6), child: Table( columnWidths: _tableColumnWidths, defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ TableRow( decoration: const BoxDecoration( color: Color(0xFFD7E9EB), ), children: excelHeader .map(_tableHeaderCell) .toList(), ), ], ), ), const SizedBox(height: 6), Table( columnWidths: _tableColumnWidths, defaultVerticalAlignment: TableCellVerticalAlignment.middle, border: const TableBorder( horizontalInside: BorderSide( color: Color(0xFFA9D9DE), width: 1, ), ), children: _paginatedExcelData.map((row) { return TableRow( children: List.generate(excelHeader.length, (index) { final cell = index < row.length ? row[index] : {'value': '-'}; return _tableDataCell(cell); }), ); }).toList(), ), ], ), ), ), ), ), ); } 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()), ), ); } Widget _buildPagination(BuildContext context) { final totalItems = filteredExcelData.length; final int startEntry = totalItems == 0 ? 0 : ((_currentPage - 1) * _rowsPerPage) + 1; int endEntry = _currentPage * _rowsPerPage; if (endEntry > totalItems) endEntry = totalItems; final totalPages = totalItems == 0 ? 1 : (filteredExcelData.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, ]; } final visiblePages = getVisiblePages(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Showing $startEntry to $endEntry of $totalItems entries', style: GoogleFonts.poppins( fontSize: 13, color: const Color(0xFF585757), fontWeight: FontWeight.w400, ), ), Row( 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: totalItems == 0 ? null : (newValue) { setState(() { _rowsPerPage = newValue!; _currentPage = 1; }); }, ), IconButton( onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, icon: const Icon(Icons.chevron_left), ), if (totalPages > 0 && !visiblePages.contains(1)) Row( children: [ _buildPageButton(1), const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('...'), ), ], ), for (final page in visiblePages) _buildPageButton(page), if (totalPages > 0 && !visiblePages.contains(totalPages)) 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), ), ], ), ], ), ); } } bool hasExcelErrorTableData(Map response) { if (response['status'] != true) return false; final data = response['data']; if (data is! Map) return false; final header = data['excel_header']; return header is List && header.isNotEmpty; } Future showExcelErrorMessageDialog( BuildContext context, String message, { bool popRouteOnClose = false, }) { return showDialog( context: context, barrierDismissible: false, builder: (dialogContext) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), title: const Text( 'Message', style: TextStyle(fontWeight: FontWeight.w600), ), content: Text(message), actions: [ TextButton( onPressed: () { Navigator.of(dialogContext).pop(); if (popRouteOnClose && context.mounted) { Navigator.of(context).pop(); } }, child: const Text('Close'), ), ], ); }, ); } Future openExcelErrorScreenIfAvailable({ required BuildContext context, required ApiService apiService, required String fileId, required String tokenType, required String clientId, required String policyNo, required String action, required String createdAt, required String clientBranchId, required String token, }) async { showDialog( context: context, barrierDismissible: false, builder: (_) => const PopScope( canPop: false, child: Center(child: CircularProgressIndicator()), ), ); try { final response = await apiService.getExcelFileErrorsApi(fileId, tokenType); if (context.mounted) { Navigator.of(context, rootNavigator: true).pop(); } if (!hasExcelErrorTableData(response)) { if (context.mounted) { await showExcelErrorMessageDialog( context, response['message']?.toString() ?? 'No error data available', ); } return; } if (!context.mounted) return; await Navigator.push( context, MaterialPageRoute( builder: (context) => excelErrorScreen( ClientId: clientId, policy_no: policyNo, action: action, created_at: createdAt, clientBranchId: clientBranchId, Token: token, TokenType: tokenType, id: fileId, ), ), ); } catch (e) { if (context.mounted) { final navigator = Navigator.of(context, rootNavigator: true); if (navigator.canPop()) { navigator.pop(); } await showExcelErrorMessageDialog( context, 'Something went wrong. Please try again.', ); } } }