import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:file_picker/file_picker.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../../core/routing/routes.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/utils/Pagination.dart'; import '../../../../data/utils/toastNotification.dart'; import '../../../layouts/main_layout.dart'; import '../../../layouts/responsive_layout.dart'; import '../../../providers/manager_provider.dart'; import '../../../themes/indicators/export_btn.dart'; import '../../../themes/indicators/filter_btn.dart'; import '../../../themes/indicators/search_field_theme.dart'; import '../../../themes/indicators/text_field_theme.dart'; import '../../../widgets/custom_action_popup.dart'; import 'agentIncentiveFile.dart'; class AgentList extends ConsumerStatefulWidget { const AgentList({super.key}); @override ConsumerState createState() => AgentListState(); } class AgentListState extends ConsumerState { int currentPage = 1; int itemsPerPage = 10; late ApiService apiService; dynamic managerId; dynamic selected_id; // List> dataVal = []; List> getAgentData = []; List> originalData = []; List> filteredData = []; bool isLoading = false; bool isRetentionImportLoading = false; dynamic sortedData; dynamic managerID; @override void initState() { super.initState(); apiService = ApiService(); // getAgentList(); Future.microtask(() { managerID = ref.read(managerIdProvider); if (managerID != null) { getAgentList(managerID); } }); } void refresh() { if (managerID != null) { getAgentList(managerID); } } // @override // void didChangeDependencies() { // super.didChangeDependencies(); // final id = ref.watch(managerIdProvider); // if (id != null) { // getAgentList(id); // } // } // List get _paginatedData { // final startIndex = (currentPage - 1) * itemsPerPage; // final endIndex = (currentPage * itemsPerPage).clamp(0, filteredData.length); // return filteredData.sublist(startIndex, endIndex); // } List get _paginatedData { // Sort descending by id first final sortedData = [...filteredData] ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); if (sortedData.isEmpty) return []; // Ensure currentPage is valid final maxPage = (sortedData.length / itemsPerPage).ceil(); final safePage = currentPage.clamp(1, maxPage); final startIndex = (safePage - 1) * itemsPerPage; final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length); return sortedData.sublist(startIndex, endIndex); } void filterData(String query) { setState(() { filteredData = getAgentData.where((item) { final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; return (item['name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['email'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['mobile'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['address'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['agent_code'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['sales_executive_name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (isActiveStatus.contains(query.toLowerCase())); }).toList(); }); } final TextEditingController _searchController = TextEditingController(); Future getAgentList(int managerId) async { print('getClaimList called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchAgentUserList(managerId); if (response['status'] == 'success') { print('getAgentListData - ${response['data']}'); setState(() { getAgentData = List>.from(response['data']); // print('API Data - $getClaimPolicies'); // originalData = getAgentData; filteredData = List.from(originalData); // print('originalData - $getClaimPolicies'); }); } else { // getClaimPolicies = []; // originalData = getClaimPolicies; // filteredData = List.from(originalData); // print('originalData - $getClaimPolicies'); // print('Request failed: ${response['code']}'); } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future _exportRetentionRateExcel() async { /****** Export confirmation dialog kept for future use ********/ // final proceed = await showDialog( // context: context, // builder: (ctx) { // return AlertDialog( // title: const Text('Export Rentation Rate'), // content: const Text( // 'The downloaded Excel will include input validation.\n\n' // 'Allowed values: only numbers from 0 to 100.\n' // 'Not allowed: negative values, values above 100, text/special characters.', // ), // actions: [ // TextButton( // onPressed: () => Navigator.pop(ctx, false), // child: const Text('Cancel'), // ), // ElevatedButton( // onPressed: () => Navigator.pop(ctx, true), // child: const Text('Download'), // ), // ], // ); // }, // ); // if (proceed != true) return; /****** *************************************************** ********/ try { await apiService.downloadAgentRetentionRateExcel(); if (mounted) { ToastHelper.showSuccessToast( context, 'Retention rate Excel downloaded', ); } } catch (e) { if (mounted) { ToastHelper.showErrorToast( context, 'Failed to export retention rate Excel', ); } } } Future _importRetentionRateExcel() async { final picked = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: const ['xls', 'xlsx'], withData: true, ); if (picked == null || picked.files.isEmpty) { return; } final file = picked.files.first; if (file.bytes == null) { if (mounted) { ToastHelper.showErrorToast(context, 'Could not read selected file'); } return; } setState(() { isRetentionImportLoading = true; }); try { final response = await apiService.importAgentRetentionRateExcel(file: file); final status = (response['status'] ?? '').toString().toLowerCase(); if (status == 'success') { final data = (response['data'] is Map) ? response['data'] as Map : {}; final report = (response['report'] is Map) ? response['report'] as Map : {}; final inserted = (data['inserted_cells'] ?? data['inserted_rows'] ?? 0); final updated = (data['updated_cells'] ?? data['updated_rows'] ?? 0); final message = (response['message'] ?? '').toString().trim(); if (mounted) { ToastHelper.showSuccessToast( context, message.isNotEmpty ? message : 'Import done', // 'Import done (Inserted: $inserted, Updated: $updated)', ); } // if (report.isNotEmpty && mounted) { // await _showRetentionImportReportDialog(data, report); // } refresh(); } else { if (mounted) { final message = (response['message'] ?? 'Import failed').toString(); ToastHelper.showErrorToast(context, message); } } } catch (e) { if (mounted) { ToastHelper.showErrorToast(context, 'Failed to import retention rate'); } } finally { if (mounted) { setState(() { isRetentionImportLoading = false; }); } } } Future _showRetentionImportReportDialog( Map data, Map report, ) async { if (!mounted) return; final inserted = data['inserted_cells'] ?? 0; final updated = data['updated_cells'] ?? 0; final skippedZero = data['skipped_zero'] ?? 0; final skippedSame = data['skipped_same'] ?? 0; final skippedEmptyRow = data['skipped_empty_row'] ?? 0; final skippedEmptyCell = data['skipped_empty_cell'] ?? 0; final skippedUnknownAgent = data['skipped_unknown_agent'] ?? 0; final invalidRange = data['invalid_range'] ?? 0; final invalidNumber = data['invalid_number'] ?? 0; final unknownAgents = (report['unknown_agent_codes'] as List?) ?.map((e) => e.toString()) .where((e) => e.trim().isNotEmpty) .toList() ?? []; final unknownVehicleTypes = ((report['unknown_vehicle_types'] ?? report['unknown_vehicle_type_names']) as List?) ?.map((e) => e.toString()) .where((e) => e.trim().isNotEmpty) .toList() ?? []; final unknownSegments = ((report['unknown_segments'] ?? report['unknown_segment_names']) as List?) ?.map((e) => e.toString()) .where((e) => e.trim().isNotEmpty) .toList() ?? []; final invalidCells = (report['invalid_cells'] as List?) ?.map((e) => e.toString()) .where((e) => e.trim().isNotEmpty) .toList() ?? []; final agentWiseSkippedRaw = (report['agent_wise_skipped_counts'] is Map) ? Map.from(report['agent_wise_skipped_counts'] as Map) : {}; final lines = [ 'Inserted: $inserted', 'Updated: $updated', // 'Skipped (value = 0): $skippedZero', // 'Skipped (Excel value equals DB value): $skippedSame', // 'Skipped (empty first column / agent code): $skippedEmptyRow', // 'Skipped (empty cell): $skippedEmptyCell', // 'Skipped (unknown agent code): $skippedUnknownAgent', 'Skipped (out of range 0-100): $invalidRange', 'Skipped (non-numeric value): $invalidNumber', ]; if (unknownAgents.isNotEmpty) { lines.add(''); lines.add('Unknown Agent Codes:'); lines.addAll(unknownAgents.take(10).map((e) => '- $e')); if (unknownAgents.length > 10) { lines.add('- ...and ${unknownAgents.length - 10} more'); } } if (unknownVehicleTypes.isNotEmpty) { lines.add(''); lines.add('Unknown Vehicle Types:'); lines.addAll(unknownVehicleTypes.take(10).map((e) => '- $e')); if (unknownVehicleTypes.length > 10) { lines.add('- ...and ${unknownVehicleTypes.length - 10} more'); } } if (unknownSegments.isNotEmpty) { lines.add(''); lines.add('Unknown Segments:'); lines.addAll(unknownSegments.take(10).map((e) => '- $e')); if (unknownSegments.length > 10) { lines.add('- ...and ${unknownSegments.length - 10} more'); } } if (invalidCells.isNotEmpty) { lines.add(''); lines.add('Invalid Cell Samples:'); lines.addAll(invalidCells.take(10).map((e) => '- $e')); if (invalidCells.length > 10) { lines.add('- ...and ${invalidCells.length - 10} more'); } } if (agentWiseSkippedRaw.isNotEmpty) { final sorted = agentWiseSkippedRaw.entries.toList() ..sort((a, b) { final av = int.tryParse(a.value.toString()) ?? 0; final bv = int.tryParse(b.value.toString()) ?? 0; return bv.compareTo(av); }); final nonZero = sorted .where((e) => (int.tryParse(e.value.toString()) ?? 0) > 0) .toList(); if (nonZero.isNotEmpty) { lines.add(''); lines.add('Skipped Count (Agent Wise):'); lines.addAll( nonZero .take(20) .map((e) => '- ${e.key}: ${int.tryParse(e.value.toString()) ?? 0}'), ); if (nonZero.length > 20) { lines.add('- ...and ${nonZero.length - 20} more'); } } } await showDialog( context: context, builder: (ctx) { return AlertDialog( title: const Text('Retention Rate Import Report'), content: SizedBox( width: 520, child: SingleChildScrollView( child: SelectableText(lines.join('\n')), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('OK'), ), ], ); }, ); } List _buildPopupMenuActions(BuildContext context, dynamic data) { return [ GestureDetector( onTap: () { Navigator.pop(context); print('EDIT - ${data['id']}'); dynamic id = data['id']; context.go(AppRoutes.agentDetailsFor(id.toString())); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.edit_sharp, color: Color(0xFF319718), size: 18), SizedBox(width: 10), Text('Edit'), ], ), ), ]; } @override Widget build(BuildContext context) { managerId = ref.watch(managerIdProvider); return MainLayout( title: "Partner", body: SelectionArea( child: Container( // color: Colors.yellow.shade50, width: MediaQuery.of(context).size.width, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ // Container( // // height: 30, // // color: Colors.red.shade50, // width: MediaQuery.of(context).size.width, // child: GestureDetector( // onTap: () { // context.go(AppRoutes.dashboard); // }, // child: Row( // crossAxisAlignment: CrossAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.start, // children: [ // // Tooltip( // // message: 'Back', // // child: IconButton( // // icon: const Icon( // // Icons.arrow_left_sharp, // // size: 25, // // color: Color(0xFF425B5B), // // ), // // onPressed: () { // // context.go(AppRoutes.dashboard); // // }, // // splashRadius: 18, // // hoverColor: Colors.black12, // // padding: const EdgeInsets.all(4), // // constraints: const BoxConstraints(), // // ), // // ), // // const SizedBox(width: 5), // spacing between icon and text // Text( // "Partner", // style: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w500, // ), // ), // ], // ), // ), // ), // SizedBox(height: 5), Expanded( child: Container( // color: Colors.green, // color: Colors.green.shade50, width: MediaQuery.of(context).size.width, padding: EdgeInsets.all(8.0), child: Column( children: [ Container( // height: 40, // color: Colors.pink, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Partner", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), ), Spacer(), ThemedSearchField( hintText: 'Search', backgroundColor: Color(0xFFFFFFFF), // backgroundColor: Color(0xFFF6F8F8), txtHeight: 30, onChanged: filterData, controller: _searchController, txtwidth: MediaQuery.of(context).size.width * 0.15, ), SizedBox(width: 10), InkWell( onTap: _exportRetentionRateExcel, child: Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: const Color(0xFF2E7D6E), borderRadius: BorderRadius.circular(8.0), ), child: Row( children: [ Icon( Icons.file_download_outlined, color: Colors.white, size: 16, ), SizedBox(width: 6), Text( 'Export Rentation Rate', style: GoogleFonts.poppins( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ), ), ), SizedBox(width: 10), InkWell( onTap: isRetentionImportLoading ? null : _importRetentionRateExcel, child: Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: const Color(0xFF2E7D6E), borderRadius: BorderRadius.circular(8.0), ), child: Row( children: [ isRetentionImportLoading ? SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Icon( Icons.file_upload_outlined, color: Colors.white, size: 16, ), SizedBox(width: 6), Text( 'Import Rentation Rate', style: GoogleFonts.poppins( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ), ), ), SizedBox(width: 10), InkWell( onTap: () { showUploadIncentiveModal(context); }, child: Container( padding: EdgeInsets.all(5.4), decoration: BoxDecoration( color: const Color(0xFF2E7D6E), // color: Color(0xFF425B5B), borderRadius: BorderRadius.circular(8.0), ), child: Row( // mainAxisSize: MainAxisSize.min, children: [ // Text( // 'Incentive File', // style: TextStyle( // color: Colors.white, // fontWeight: FontWeight.w600, // fontSize: 12, // ), // ), // SizedBox(width: 10), Tooltip( message: 'Incentive File', child: Icon( Icons.file_open_outlined, color: Colors.white, size: 18, ), ), ], ), ), ), SizedBox(width: 10), ExportBtn( sheetName: "Partner", fileName: "partner_list", txt: !ResponsiveLayout.isMobile(context) ? true : false, data: filteredData, displayHeaders: [ 'S.No.', 'Partner Id', 'Partner Name', 'Email', 'Sales Executive Name', 'Phone Number', 'Address', 'Status', ], keys: [ "sno", // handled internally as i + 1 "agent_code", "name", "email", "sales_executive_name", "mobile", "address", "is_active", ], ), SizedBox(width: 10), InkWell( onTap: () { context.go(AppRoutes.agentDetailsFor('new')); }, child: Container( padding: EdgeInsets.all(5.0), decoration: BoxDecoration( // color: Color(0xFF425B5B), color: const Color(0xFF2E7D6E), borderRadius: BorderRadius.circular(8.0), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ // Text( // 'Create New Partner', // style: TextStyle( // color: Colors.white, // fontWeight: FontWeight.w600, // fontSize: 12, // ), // ), // SizedBox(width: 10), Tooltip( message: 'Create New Partner', child: Icon( Icons.add, color: Colors.white, size: 18, ), ), ], ), ), ), ], ), ), SizedBox(height: 10), Container( // height: 50, decoration: BoxDecoration( color: Color(0xFFF1F5F9), // color: Color(0xFFEDF6F5), borderRadius: BorderRadius.circular(6), ), padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 16, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( flex: 1, child: Text('S.No.', style: _headerStyle), ), Expanded( flex: 1, child: Text('Partner Id', style: _headerStyle), ), Expanded( flex: 2, child: Text('Partner Name', style: _headerStyle), ), Expanded( flex: 3, child: Text('Email', style: _headerStyle), ), Expanded( flex: 2, child: Text('Phone Number', style: _headerStyle), ), Expanded( flex: 2, child: Text('Sales Executive Name', style: _headerStyle), ), Expanded( flex: 2, child: Text('Address', style: _headerStyle), ), Expanded( flex: 1, child: Text('Status', style: _headerStyle), ), Expanded( flex: 1, child: Text('Action', style: _headerStyle), ), ], ), ), Expanded( child: Container( color: Colors.white, child: _buildClaimsDataTable(context), ), ), ], ), ), ), Container( // height: 20, width: MediaQuery.of(context).size.width, // color: Colors.green.shade50, child: PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, // totalItems: dataVal.length, totalItems: filteredData.length, // activeColor: layoutColor, // your theme color onPageChanged: (page) { setState(() { currentPage = page; }); }, onItemsPerPageChanged: (items) { setState(() { itemsPerPage = items; currentPage = 1; }); }, ), ), ], ), ),), ); } Widget _buildClaimsDataTable(BuildContext context) { if (filteredData.isEmpty) { return const SizedBox( height: 50, child: Center(child: Text('No available data')), ); } // final sortedData = [..._paginatedData] // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); final sortedData = [..._paginatedData]; // final sortedData = [...filteredData] // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); return ListView.builder( // itemCount: filteredData.length + 1, // +1 for header, +1 for pagination itemCount: sortedData.length + 1, // +1 for header, +1 for pagination itemBuilder: (context, index) { if (index == 0) return _buildHeader(); // if (index == dataVal.length + 1) // return _buildPagination(context); final startIndex = ((currentPage - 1) * itemsPerPage); // final item = filteredData[index - 1]; final item = sortedData[index - 1]; final sno = startIndex + index; return _buildDataRow(item, sno); }, ); } Widget _buildHeader() { return const SizedBox.shrink(); // empty widget instead of commenting } Widget _buildDataRow(Map item, sno) { return Container( padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 16), // margin: const EdgeInsets.only(top: 10), decoration: BoxDecoration( color: Colors.white, // color: Color(0xFFE0F7F9), border: const Border( bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1), ), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)), Expanded(flex: 1, child: Text(item['agent_code'] ?? '-', style: _dataBold)), Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)), Expanded( flex: 3, child: Text(item['email'] ?? '-', style: _dataBold), ), Expanded( flex: 2, child: Text(item['mobile'] ?? '-', style: _dataBold), ), Expanded( flex: 2, child: Text(item['sales_executive_name'] ?? '-', style: _dataBold), ), Expanded( flex: 2, child: Text( item['address'] ?? '-', style: _dataBold, softWrap: true, maxLines: 3, ), ), Expanded( flex: 1, child: Row( children: [ Transform.scale( scale: 0.4, // reduce size (0.7–0.9 works well) child: Switch( value: item['is_active'] == "1", onChanged: (val) { setState(() { item['is_active'] = val ? "1" : "0"; }); final response = apiService.updateStatus( item['id'], val ? "0" : "1", 'agent', ); print("Response - $response"); }, // activeColor: Color(0xFF425B5B), // activeTrackColor: Color(0xFFB2D8D3), activeColor: Color(0xFF2E7D6E), // thumb when active activeTrackColor: Color(0xFFDCFCE7), // track when active inactiveThumbColor: Colors.grey.shade400, // thumb when inactive inactiveTrackColor: Colors.grey.shade300, // track when inactive ), ), ], ), ), Expanded( flex: 1, child: item['is_active'] == "1" ? Row( children: [ Tooltip( message: 'Edit', child: IconButton( icon: Image.asset( "assets/miscellaneous/Edit.png", height: 12, width: 15, ), onPressed: () { print('EDITStaff - ${item['id']}'); selected_id = item['id']; context.go(AppRoutes.agentDetailsFor(selected_id.toString())); }, splashRadius: 28, hoverColor: Colors.black12, padding: const EdgeInsets.all(8), constraints: const BoxConstraints(), ), ), ], ) : Row( children: [ Image.asset( "assets/miscellaneous/Edit_muted.png", height: 12, width: 15, ), ], ), ), // Expanded( // flex: 1, // child: Row( // children: [ // PopupMenuButton( // color: Colors.white, // padding: EdgeInsets.zero, // offset: Offset(0, 30), // icon: Icon( // Icons.more_vert, // color: Color(0xFF475569), // size: 14, // ), // itemBuilder: (context) => [ // if (item['is_active'] == "1") // CustomPopupMenuEntry( // child: Container( // padding: EdgeInsets.symmetric( // horizontal: 8, // vertical: 8, // ), // child: Column( // mainAxisSize: MainAxisSize.min, // mainAxisAlignment: MainAxisAlignment.center, // children: _buildPopupMenuActions(context, item), // ), // ), // ), // ], // ), // ], // ), // ), ], ), ); } static final _dataBold = GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); static final _dataSub = TextStyle( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); static final _headerStyle = GoogleFonts.poppins( fontSize: 11.2, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), ); }