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'; 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 = 5; 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); // 🔴 CASE 1: Empty data → popup + back if (response['data'] is List && response['data'].isEmpty) { setState(() => isLoading = false); _showEmptyDataDialog(response['message']); return; } // 🟢 CASE 2: Success with data if (response['status'] == true) { ToastHelper.showSuccessToast(context, response['message']); setState(() { isLoading = false; isSuccess = false; excelValidationStaus = 1; excelHeader = List.from(response['data']['excel_header']); excelData = (response['data']['excel_data'] as List) .map>>( (row) => row .map>( (cell) => Map.from(cell)) .toList(), ) .toList(); filteredExcelData = List.from(excelData); }); } // 🟡 CASE 3: API failed with message else { setState(() => isLoading = false); _showEmptyDataDialog(response['message']); } } catch (e) { setState(() => isLoading = false); print('Exception occurred: $e'); _showEmptyDataDialog('Something went wrong. Please try again.'); } } void _showEmptyDataDialog(String message) { showDialog( context: context, barrierDismissible: false, builder: (context) { 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(context).pop(); // close dialog Navigator.of(context).pop(); // go back page }, child: const Text('Close'), ), ], ); }, ); } 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 { // print('handleExportAction'); // // final postId = await tokenService.readValue('empHrId'); // final preId = await tokenService.readValue('enrollmentEmpPrimaryId'); // // var activity = "export_empdata"; // // var activityPre = "export_preempdata"; // dynamic response; // // print('postId - $postId'); // print('preId - $preId'); // print('activity - $activity'); // // try { // print('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') { // 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'); // } // } 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) { return 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 ), ) : 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 ScrollConfiguration( behavior: const MaterialScrollBehavior().copyWith( dragDevices: { PointerDeviceKind.mouse, PointerDeviceKind.touch, PointerDeviceKind.trackpad, }, ), child: _buildScrollableTable(context), ); } Widget _buildScrollableTable(BuildContext context) { const double columnWidth = 160; final double tableWidth = excelHeader.length * columnWidth; return Scrollbar( thumbVisibility: true, controller: _verticalController, child: SingleChildScrollView( controller: _verticalController, physics: const ClampingScrollPhysics(), // 👈 mouse wheel 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: SizedBox( width: tableWidth, child: Column( children: [ Container( decoration: BoxDecoration( color: const Color(0xFFD7E9EB), borderRadius: BorderRadius.circular(6), ), padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: excelHeader.map((header) { return SizedBox( width: columnWidth, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( header, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ); }).toList(), ), ), const SizedBox(height: 6), /// ROWS ..._paginatedExcelData.map((row) { return Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: const BoxDecoration( border: Border( bottom: BorderSide( color: Color(0xFFA9D9DE), width: 1, ), ), ), child: Row( children: row.map((cell) { final bool hasError = cell.containsKey('error'); return SizedBox( width: columnWidth, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: hasError ? Row( children: [ Expanded( child: Text( cell['value']?.toString() ?? '-', style: GoogleFonts.poppins( fontSize: 12, ), ), ), const SizedBox(width: 6), IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), icon: const Icon( Icons.error_outline, color: Colors.red, size: 16, ), onPressed: () { showDialog( context: context, barrierDismissible: true, builder: (_) { final List errors = cell['error'] as List; 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: [ /// ERROR TITLE Text( 'Error!', style: GoogleFonts.poppins( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black, ), ), const SizedBox(height: 16), /// RED ICON 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), /// ERROR HEADING (optional – first error) 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), /// ERROR DETAILS ...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), // orange text ), ), ), ), const SizedBox(height: 20), /// OK BUTTON 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, ), ), ), ), ], ), ), ); }, ); }, ), ], ) : Text( cell['value']?.toString() ?? '-', style: GoogleFonts.poppins( fontSize: 12, ), ), ), ); }).toList(), ), ); }).toList(), /// PAGINATION SizedBox( width: tableWidth, child: _buildPagination(context), ), ], ), ), ), ), ), ); } Widget _buildPagination(BuildContext context) { final totalPages = (filteredExcelData.length / _rowsPerPage).ceil(); if (totalPages <= 1) { return const SizedBox.shrink(); // 👈 hide if only one page } return Row( mainAxisAlignment: MainAxisAlignment.end, // 👈 right aligned children: [ DropdownButton( value: _rowsPerPage, items: [5, 10, 15, 20, 50].map((int value) { return DropdownMenuItem( value: value, child: Text( ' $value ', style: GoogleFonts.poppins(fontSize: 14), ), ); }).toList(), onChanged: (newValue) { setState(() { _rowsPerPage = newValue!; _currentPage = 1; }); }, ), IconButton( icon: const Icon(Icons.chevron_left), onPressed: _currentPage > 1 ? () => setState(() => _currentPage--) : null, ), for (int i = 1; i <= totalPages; i++) Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: _currentPage == i ? const Color(0xFF00A6A6) : Colors.grey[300], foregroundColor: _currentPage == i ? Colors.white : Colors.black, minimumSize: const Size(36, 36), padding: EdgeInsets.zero, ), onPressed: () { setState(() { _currentPage = i; }); }, child: Text(i.toString()), ), ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: _currentPage < totalPages ? () => setState(() => _currentPage++) : null, ), ], ); } }