import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:nhance_partner/data/utils/toastNotification.dart'; import '../../../core/routing/routes.dart'; import '../../../core/services/api_service.dart'; import '../../layouts/main_layout.dart'; import '../../layouts/responsive_layout.dart'; import '../../providers/manager_provider.dart'; import '../../providers/userRoleProvider.dart'; import '../../themes/indicators/input_field_decoration.dart'; import '../../themes/indicators/search_field_theme.dart'; import 'FormFieldBox.dart'; import 'custom_dateRange.dart'; class PayOutDetails extends ConsumerStatefulWidget { final Map? editItem; const PayOutDetails({super.key, this.editItem}); @override ConsumerState createState() => _PayOutDetailsState(); } class _PayOutDetailsState extends ConsumerState { static final List _commissionInputFormatter = [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')), ]; static final List _utrInputFormatter = [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')), ]; // -------------------------- // Invoice Fields // -------------------------- final _formKey = GlobalKey(); // final TextEditingController invoiceNoController; DateTime invoiceDate = DateTime.now(); // dynamic selectedAgentId; List? selectedAgentId; dynamic selectedBrokerID; DateTime? policyTillDate = DateTime.now(); dynamic totalPolicies = '0'; dynamic totalCommission = '0'; bool isEdit = false; List> masterPolicies = []; final TextEditingController _searchStaffController = TextEditingController(); // -------------------------- // Filters // -------------------------- DateTime? fromDate; DateTime? toDate; Map controllers = {}; List tabHeader = [ 'startDate', 'endDate', 'invoiceNo', 'invoiceDate', 'invoiceStatus', 'utrNumber', ]; // -------------------------- // Collapse States // -------------------------- bool invoiceCollapsed = false; bool policyCollapsed = false; // -------------------------- // Sample Data (replace with API later) // -------------------------- final List> allPolicies = []; List> filteredPolicies = []; Set selectedPolicies = {}; List> getBrokerData = []; List> filteredBrokerData = []; List> getAgentData = []; List> filteredAgentData = []; bool isLoading = false; bool isLoadingEditData = false; bool hasFetchedTableData = false; final Set _inlineSavingIds = {}; late ApiService apiService; dynamic managerId; dynamic userId; dynamic roleId; final DateFormat df = DateFormat('dd-MM-yyyy'); @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } Future.microtask(() async { managerId = ref.read(managerIdProvider); roleId = ref.read(userRoleProvider); userId = ref.read(userIdProvider); print('PD =>PayOutDetails => managerId: $managerId'); if (managerId != null) { getAgentList(managerId); } await getBroker(); // ✅ EDIT MODE (MUST be here) if (widget.editItem != null) { print('PD => editItemID ${widget.editItem}'); final invoiceID = widget.editItem!['id']; controllers['invoiceNo']?.text = widget.editItem!['invoice_no']; controllers['invoiceDate']?.text = widget.editItem!['invoice_date_ui_format']; controllers['invoiceStatus']?.text = widget.editItem!['payout_status']; _loadEditData(invoiceID, managerId); // ✅ managerId ready } }); } void filterPolicyData(String query) { final lowerQuery = query.toLowerCase(); print('PD => filterPolicyData - $lowerQuery'); print('PD => filteredPolicies1 - $filteredPolicies'); setState(() { if (query.trim().isEmpty) { print('PD => filteredPolicies2'); filteredPolicies = List.from(masterPolicies); return; } print('PD => filteredPolicies3'); filteredPolicies = masterPolicies.where((item) { return (item['policy_no'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (item['customer_name'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (item['agent_name'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (item['insurer_name'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (item['premium_amount'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (item['commission_amount'] ?? '').toString().toLowerCase().contains( lowerQuery, ) || (_formatDate(item['issued_date']) ?? '').toLowerCase().contains( lowerQuery, ); }).toList(); }); } void refresh() { setState(() { for (var controller in controllers.values) { controller.clear(); controller.dispose(); } selectedBrokerID = ''; selectedAgentId = []; selectedPolicies = {}; totalPolicies = ''; totalCommission = ''; filteredPolicies = []; hasFetchedTableData = false; // dropDownKeyPartner.currentState?.clear(); }); } void _loadEditData(String invoiceID, managerID) async { setState(() => isLoadingEditData = true); print("H010 => r : $roleId | mId: $managerID | ParmMID: $managerID | uId: $userId "); print('PD => _loadEditData'); final jsondata = { // "id": invoiceID, "invoice_id": invoiceID, "manager_id": managerId // "issued_date": formattedTillDate, }; print('PD => _loadEditData - $jsondata'); final response = await apiService.getCommissionRateList(jsondata); // final response = await apiService.getInvoiceDetails(invoiceID); if (response['status'] == 'success') { print('PD => _loadEditData success'); print('PD => _loadEditData success - ${response['data']}'); final invoice = response['data']; print("Invoice → $invoice"); // final items = List>.from(response['data']['items']); final items = response['data']; final totPolicy = response['total_policies'].toString() ?? "0"; final totalCommiss = response['total_commission']; setState(() { // selectedPolicies = response['total_policies']; isEdit = true; totalPolicies = totPolicy; totalCommission = totalCommiss; // filteredPolicies = items.map((p) { // final policyId = int.parse(p['policy_id']); // return {...p, 'is_selected': selectedPolicies.contains(policyId)}; // }).toList(); final List> items = (response['data'] as List) .map((e) => Map.from(e)) .toList(); masterPolicies = List>.from(response['data']); filteredPolicies = List>.from(response['data']); print("selectedPolicies → $filteredPolicies "); }); print( "selectedPolicies → $selectedPolicies ->${response['total_policies']}", ); print("Items → $items"); // ----------------------------- // 1. SET BASIC FIELDS // ----------------------------- // setState(() { // // selectedAgentId = int.tryParse(invoice['agent_id'].toString()); // // // invoiceNoController.text = invoice['invoice_no']; // // invoiceDate = DateTime.parse(invoice['invoice_date']); // // controllers['startDate']?.text = invoice['from_date']; // controllers['endDate']?.text = invoice['to_date']; // selectedBrokerID = int.tryParse(invoice['broker_id'].toString()); // if (invoice['agent_id'] is List) { // selectedAgentId = (invoice['agent_id'] as List) // .map((e) => e.toString()) // .toList(); // } else if (invoice['agent_id'] is String) { // final raw = invoice['agent_id'].toString(); // // if (raw.startsWith('[')) { // // JSON string: "[1,2]" // selectedAgentId = List.from( // jsonDecode(raw).map((e) => e.toString()), // ); // } else if (raw.contains(',')) { // // Comma separated: "1,2" // selectedAgentId = raw.split(',').map((e) => e.trim()).toList(); // } else if (raw.isNotEmpty) { // // Single value: "1" // selectedAgentId = [raw]; // } else { // selectedAgentId = []; // } // } else { // selectedAgentId = []; // } // // // if (invoice['till_date'] != "0000-00-00") { // // policyTillDate = DateTime.parse(invoice['till_date']); // // } else { // // policyTillDate = DateTime.now(); // // } // }); // print('PD => selectedBrokerID $selectedBrokerID'); // ----------------------------- // 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT // ----------------------------- // await loadPolicies(); // ----------------------------- // 2. SET SELECTED POLICIES // ----------------------------- // selectedPolicies = items // .map((p) => int.parse(p['policy_id'].toString())) // .toSet(); // ----------------------------- // 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT // ----------------------------- // await loadPolicies(); // ----------------------------- // 4. KEEP ONLY POLICIES THAT MATCH API DATA // ----------------------------- // setState(() { // filteredPolicies = filteredPolicies.map((p) { // final policyId = int.parse(p['policy_id']); // return {...p, 'is_selected': selectedPolicies.contains(policyId)}; // }).toList(); // }); } setState(() => isLoadingEditData = false); } String formatDate(String date) { final d = DateTime.parse(date); return DateFormat('dd-MM-yyyy').format(d); } // Get Broker List Future getBroker() async { print('PD =>getBroker called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Broker'); if (response['status'] == 200) { print('PD =>getBroker - ${response['data']}'); setState(() { getBrokerData = List>.from(response['data']); print('PD =>API Data - $getBrokerData'); filteredBrokerData = List.from(getBrokerData); print('PD =>originalData - $filteredBrokerData'); }); } else { getBrokerData = []; filteredBrokerData = []; } } catch (e) { print('PD =>Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } //Get Agent List Future getAgentList(int id) async { print('PD =>getClaimList called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchAgentUserList(managerId); if (response['status'] == 'success') { print('PD =>getAgentListData - ${response['data']}'); setState(() { getAgentData = List>.from(response['data']); print('PD =>API Data - $getAgentData'); filteredAgentData = List.from(getAgentData); print('PD =>originalData - $filteredAgentData'); }); } else { getAgentData = []; filteredAgentData = []; } } catch (e) { print('PD =>Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } // Generate invoice number String generateInvoiceNumber() { final now = DateTime.now(); final random = now.millisecondsSinceEpoch % 1000; return "INV${now.year}${now.month.toString().padLeft(2, '0')}${random.toString().padLeft(3, '0')}"; } // Pick date helper Future pickDate(DateTime initial) async { return await showDatePicker( context: context, initialDate: initial, firstDate: DateTime(2000), lastDate: DateTime(2100), ); } // if (dashboardKey == 'f' && // isDashboardInitialLoad && // SelectedStatus == "P") { // // // fromDt = "";toDt = ""; // isDashboardInitialLoad = false; // // } else if (dashboardKey == 'f' && isDashboardInitialLoad && SelectedStatus != '') { // fromDt = "";toDt = ""; // isDashboardInitialLoad = false; // } else if (fromDateVal.isEmpty && toDateVal.isEmpty) { // // Default fallback // final today = DateTime.now(); // fromDt = DateFormat('dd-MM-yyyy').format(today.subtract(const Duration(days: 5))); // toDt = DateFormat('dd-MM-yyyy').format(today); // } else { // // ✅ USER SELECTED DATE — ALWAYS RESPECT THIS // fromDt = fromDateVal; // toDt = toDateVal; // } // Load policies (simulate API) Future loadPolicies() async { print('PD =>loadPolicies'); print("H13 => r : $roleId | mId: $managerId | uId: $userId "); setState(() { isLoading = true; hasFetchedTableData = false; }); try { final List agentIds = (selectedAgentId ?? []) .map((e) => int.parse(e)) .toList(); final jsondata = { "from_date": controllers['startDate']?.text, "to_date": controllers['endDate']?.text, "manager_id": managerId, if (agentIds != null && agentIds.isNotEmpty) "agent_id": agentIds, // "agent_id": agentIds, // "issued_date": formattedTillDate, }; final response = await apiService.getCommissionRateList(jsondata); if (response['status'] == 'success') { print('PD =>API Data response - $response'); setState(() { filteredPolicies = List>.from(response['data']); hasFetchedTableData = true; print('PD =>API Data - $filteredPolicies'); // filteredPolicies = allPolicies.where((p) { // if (p["agentId"] != selectedAgentId) return false; // if (policyTillDate != null && // DateTime.parse(p["date"]).isAfter(policyTillDate!)) // return false; // return true; // }).toList(); }); } else { setState(() { filteredPolicies = []; hasFetchedTableData = true; }); } } catch (e) { print('PD =>Exception occurred: $e'); setState(() { filteredPolicies = []; hasFetchedTableData = false; }); } finally { setState(() { isLoading = false; }); } } // Apply date filters void filterPolicies() { if (selectedAgentId == null) return; setState(() { filteredPolicies = allPolicies.where((p) { final date = DateTime.parse(p["date"]); // if (p["agentId"] != selectedAgentId) return false; // if (policyTillDate != null && date.isAfter(policyTillDate!)) // return false; if (fromDate != null && date.isBefore(fromDate!)) return false; if (toDate != null && date.isAfter(toDate!)) return false; return true; }).toList(); }); } // Save invoice (simulation) Future saveInvoice() async { try { if (selectedAgentId == null) { ToastHelper.showWarningToast( context, "Please select a partner before saving.", ); return; } if (selectedPolicies.isEmpty) { ToastHelper.showWarningToast( context, "Please select at least one policy before saving.", ); return; } final String utrInput = (controllers['utrNumber']?.text ?? '').trim(); if (utrInput.isEmpty) { ToastHelper.showWarningToast( context, "Please enter the UTR number. It cannot be left empty.", ); return; } // Format dates for API final String formattedInvoiceDate = DateFormat( 'yyyy-MM-dd', ).format(invoiceDate); final String formattedTillDate = DateFormat( 'yyyy-MM-dd', ).format(policyTillDate!); final List> selectedRows = filteredPolicies .where((p) { final id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; return id != 0 && selectedPolicies.contains(id); }) .toList(); if (selectedRows.isEmpty) { ToastHelper.showWarningToast( context, "No matching policies found for your selection. Refresh the list and try again.", ); return; } for (final p in selectedRows) { final policyNo = (p['policy_no'] ?? '-').toString(); final rawPayout = (p['commission_amount'] ?? '').toString().trim(); if (rawPayout.isEmpty) { ToastHelper.showWarningToast( context, "Payout amount is missing for policy $policyNo. Enter an amount for every selected policy.", ); return; } final payout = double.tryParse(rawPayout); if (payout == null) { ToastHelper.showWarningToast( context, "Payout amount for policy $policyNo is not a valid number. Use digits only (e.g. 1500 or 1500.50).", ); return; } final premium = _parsePremiumAmount(p['premium_amount']); if (premium != null && payout > premium + 0.001) { ToastHelper.showWarningToast( context, "Payout for policy $policyNo (₹${payout.toStringAsFixed(2)}) cannot be greater than the premium (₹${premium.toStringAsFixed(2)}).", ); return; } } final double totalPayout = selectedRows.fold( 0.0, (sum, p) => sum + _parseCommissionValue(p["commission_amount"]), ); // Build items array final List> items = selectedRows .map( (p) => { "policy_id": int.parse(p["policy_id"].toString()), "policy_no": p["policy_no"], "commission_amount": _parseCommissionValue(p["commission_amount"]), }, ) .toList(); final List> utrs = _buildUtrPayloadForSelectedPolicies( defaultUtrDate: formattedInvoiceDate, ); if (utrs.isEmpty) { ToastHelper.showWarningToast( context, "UTR details could not be built. Check the UTR number and selected policies.", ); return; } final double totalUtrAmount = _sumUtrAmounts(utrs); if (!_amountsMatch(totalPayout, totalUtrAmount)) { ToastHelper.showWarningToast( context, "Total payout (₹${totalPayout.toStringAsFixed(2)}) must match the total UTR amount (₹${totalUtrAmount.toStringAsFixed(2)}). " "Check each row's payout and the UTR field.", ); return; } final int payoutStatus = _amountsMatch(totalPayout, totalUtrAmount) ? 2 : 1; final jsondata = { "invoice_no": controllers['invoiceNo']?.text, "invoice_amount": totalPayout, "agent_id": selectedAgentId, "broker_id": selectedBrokerID, "invoice_date": formattedInvoiceDate, "till_date": formattedTillDate, "payout_status": payoutStatus, "pos_id": null, "created_by": userId, "updated_by": userId, "items": items, "utrs": utrs, }; print('PD =>Commissions $jsondata'); final bool confirm = await showSaveConfirmation(context); if (!confirm) return; final response = await apiService.getCreateOrUpdate(jsondata); if (response['status'] == 'success') { setState(() { print('PD =>API Data response - $response'); selectedPolicies.clear(); controllers['invoiceNo']?.clear(); controllers['utrNumber']?.clear(); invoiceDate = DateTime.now(); policyTillDate = DateTime.now(); filteredPolicies = []; }); context.go(AppRoutes.payoutList); } else { final msg = (response['message'] ?? response['data'] ?? 'Save failed') .toString(); ToastHelper.showErrorToast(context, msg); } } catch (e) { print('PD =>Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } String _displayText(dynamic value) { if (value == null) return '-'; final text = value.toString().trim(); if (text.isEmpty || text.toLowerCase() == 'null') return '-'; return text; } List> _buildUtrPayloadForSelectedPolicies({ required String defaultUtrDate, }) { final Map utrAmountMap = {}; final String enteredUtr = (controllers['utrNumber']?.text ?? '').trim(); for (final policy in filteredPolicies) { final int policyId = int.tryParse(policy["policy_id"]?.toString() ?? "0") ?? 0; if (!selectedPolicies.contains(policyId)) continue; final String utrNo = enteredUtr.isNotEmpty ? enteredUtr : (policy['utr_no'] ?? '').toString().trim(); if (utrNo.isEmpty) continue; final double amount = _parseCommissionValue(policy["commission_amount"]); utrAmountMap[utrNo] = (utrAmountMap[utrNo] ?? 0) + amount; } return utrAmountMap.entries .map( (e) => { "utr_no": e.key, "amount": e.value, "utr_date": defaultUtrDate, }, ) .toList(); } void showMessage(String msg) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } Future showSaveConfirmation(BuildContext context) async { return await showDialog( context: context, barrierDismissible: false, builder: (context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), title: Text( "Confirm Save", style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, ), ), content: Text( "Once saved, payout details cannot be edited.\nAre you sure you want to continue?", style: GoogleFonts.inter(fontSize: 13, color: Colors.grey[700]), ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text( "No", style: GoogleFonts.inter( fontWeight: FontWeight.w500, color: Colors.grey[700], ), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF059669), // emerald shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), onPressed: () => Navigator.pop(context, true), child: Text( "Yes, Save", style: GoogleFonts.inter( fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ); }, ) ?? false; } @override Widget build(BuildContext context) { final df = DateFormat("dd-MM-yyyy"); final int status = int.tryParse(controllers['invoiceStatus']?.text ?? '0') ?? 0; final bool isPending = status == 1; return MainLayout( title: "Pay Out", body: !isLoadingEditData ? Container( color: Colors.white, padding: const EdgeInsets.all(8), margin: isEdit ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, ) : null, child: Column( children: [ // const SizedBox(height: 20), Container( height: 40, width: MediaQuery.of(context).size.width, child: GestureDetector( onTap: null, 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.payoutList); }, splashRadius: 18, hoverColor: Colors.black12, padding: const EdgeInsets.all(4), constraints: const BoxConstraints(), ), ), const SizedBox( width: 5, ), // spacing between icon and text Text( "Payout Details", style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, ), ), // 🟢 Status Chip if (isEdit) ...[ Spacer(), Text( controllers['invoiceNo']?.text ?? '', style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w500, color: const Color(0xFF374151), ), ), SizedBox(width: 10), Text( // controllers['invoiceDate']?.text ?? '', '(${controllers['invoiceDate']?.text ?? ''})', style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w300, color: const Color(0xFF6B7280), ), ), // SizedBox(width: 10), // Spacer(), SizedBox(width: 10), Text( 'Status: ${isPending ? 'Pending' : 'Completed'}', style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w300, color: const Color(0xFF6B7280), // color: isPending // ? Colors.blueAccent // : const Color(0xFF047857), ), ), Spacer(), ThemedSearchField( hintText: 'Search', // backgroundColor: Color(0xFFF6F8F8), onChanged: filterPolicyData, controller: _searchStaffController, backgroundColor: Color(0xFFFFFFFF), txtHeight: 30, txtwidth: ResponsiveLayout.isMobile(context) ? MediaQuery.of(context).size.width * 0.7 : MediaQuery.of(context).size.width * 0.15, ), ], if (!isEdit) ...[ const Spacer(), if (hasFetchedTableData) ...[ const SizedBox(width: 10), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: const Color(0xFFECFDF5), // emerald bg borderRadius: BorderRadius.circular(20), ), child: Row( children: [ Text( "SELECTED POLICIES : ", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, color: const Color(0xFF065F46), letterSpacing: 0.4, ), ), const SizedBox(width: 6), Text( totalPolicies, style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w700, color: const Color(0xFF047857), ), ), ], ), ), const SizedBox(width: 10), // 💰 TOTAL COMMISSION Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: const Color(0xFF10B981), // emerald green borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( color: const Color( 0xFF10B981, ).withOpacity(0.25), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( children: [ const Icon( Icons.account_balance_wallet_outlined, size: 14, color: Colors.white, ), const SizedBox(width: 6), Text( "TOTAL PAYOUT AMOUNT :", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, color: Colors.white.withOpacity(0.9), letterSpacing: 0.4, ), ), const SizedBox(width: 6), Text( "₹$totalCommission", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white, ), ), ], ), ), const SizedBox(width: 10), ], ], ], ), ), ), // SizedBox(height: 5), if (!isEdit) ...[ Container( padding: EdgeInsets.all(8.0), child: DateFilterRowPayout( formKey: _formKey, // key: ValueKey(SelectedStatus ?? ''), role: roleId, id: userId, showBrokerFilter: false, selectedParnter: selectedAgentId, selectedBroker: selectedBrokerID, onPartnerChanges: (val) { setState(() { selectedAgentId = val; // ✅ parent state updated }); }, selectedStaffId: '', startController: controllers['startDate']!, endController: controllers['endDate']!, isMobile: ResponsiveLayout.isMobile(context), onFilter: () { print( 'DATEROw- ${controllers['startDate']!} -${controllers['endDate']!}' ' - $selectedAgentId', ); // call your filter logic // filterDateRange(); loadPolicies(); }, onRefresh: () { // call your refresh logic refresh(); }, ), ), SizedBox(height: 10), ], // Policy Selection Card( // color: Colors.amber, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, child: Column( children: [ AnimatedCrossFade( duration: const Duration(milliseconds: 300), crossFadeState: policyCollapsed ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: const SizedBox(), secondChild: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: _tableMinWidth, child: Column( children: [ _policyTableHeader(), // static header _policyTableBody(), // vertical scroll rows ], ), ), ), ), ], ), ), if (selectedPolicies.isNotEmpty) ...[ Container( padding: const EdgeInsets.all(8), // color: Colors.amber, color: Color(0xFFf9fafb), // color: Colors.white, child: Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // ---------------- SELECTED COUNT ---------------- Row( // crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( // selectedPolicies.length.toString(), totalPolicies, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "policies selected", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "|", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "Total Payout Amount", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), /// NULL-SAFE TOTAL COMMISSION CALCULATION Text( // "₹ ${_calculateTotalCommission()}", "₹ $totalCommission", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.teal, ), ), ], ), Spacer(), buildUTRNumber(context), SizedBox(width: 20), // ---------------- BUTTONS ---------------- Row( children: [ ElevatedButton( onPressed: saveInvoice, style: ElevatedButton.styleFrom( // backgroundColor: const Color(0xff45a049), backgroundColor: Colors.teal, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 4, ), // 🔽 reduced radius ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10, ), ), child: Text( "Save Payout Details", style: GoogleFonts.poppins( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 10), TextButton( onPressed: () => setState(() => selectedPolicies.clear()), child: const Text("Clear"), ), ], ), ], ), ), ], if (isEdit) ...[ Container( padding: const EdgeInsets.all(8), // color: Colors.amber, color: Color(0xFFf9fafb), // color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.end, // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( // selectedPolicies.length.toString(), totalPolicies, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "policies selected", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "|", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( "Total Payout Amount", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), /// NULL-SAFE TOTAL COMMISSION CALCULATION Text( // "₹ ${_calculateTotalCommission()}", "₹ $totalCommission", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.teal, ), ), const SizedBox(width: 10), ], ), ), ], // ---------------------- // const SizedBox(height: 100), ], ), ) : Container( color: Colors.white, height: MediaQuery.of(context).size.height * 0.7, child: Center(child: CircularProgressIndicator()), ), ); } Widget _policyTableHeader() { final allSelected = filteredPolicies.isNotEmpty && filteredPolicies.every( (p) => selectedPolicies.contains( int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0, ), ); // filteredPolicies.every( // (p) => selectedPolicies.contains( // int.tryParse(p["policy_id"].toString()) ?? 0, // ), // ); final partiallySelected = selectedPolicies.isNotEmpty && !allSelected; return Container( height: 30, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: const BoxDecoration( // color: Color(0xFFf9fafb), color: Color(0xFFf9fafb), // color: Color(0xFFf9fafb), // color: Color(0xFFF6F8F7), // border: Border(bottom: BorderSide(color: Color(0xFFE0E0E0))), // border: Border(bottom: BorderSide(color: Color(0xFFECEFF1))), border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))), ), child: Row( children: [ if (!isEdit) ...[ SizedBox( width: colCheck, child: Checkbox( value: allSelected ? true : (partiallySelected ? null : false), tristate: true, onChanged: (v) { setState(() { if (v == true) { selectedPolicies = filteredPolicies .map( (p) => int.tryParse( p["policy_id"]?.toString() ?? "0", ) ?? 0, ) .where((id) => id != 0) // Filter out invalid IDs .toSet(); // selectedPolicies = filteredPolicies // .map( // (p) => int.tryParse(p["policy_id"].toString()) ?? 0, // ) // .toSet(); } else { selectedPolicies.clear(); } totalPolicies = selectedPolicies.length.toString(); }); _calculateTotalCommission(); }, ), ), ], _headerText("POLICY NO", colPolicy), _headerText("POLICY DATE", colDate), _headerText("PARTNER", colAgent), _headerText("CUSTOMER", colCustomer), _headerText("PREMIUM", colPremium), _headerText("PAYOUT", colCommission), ], ), ); } Widget _headerText(String text, double width) { return SizedBox( width: width, child: Text( text, style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w500, color: Colors.blueGrey, letterSpacing: 0.5, ), ), ); } Widget _policyTableBody() { return SizedBox( // height: isEdit ? 400 : 340, // avoids overflow height: isEdit ? MediaQuery.of(context).size.height * 0.65 : MediaQuery.of(context).size.height * 0.48, // avoids overflow child: ListView.builder( itemCount: filteredPolicies.length, itemBuilder: (context, index) { final p = filteredPolicies[index]; final int id = int.tryParse(p["policy_id"]?.toString() ?? "0") ?? 0; final bool isSelected = selectedPolicies.contains(id); return InkWell( onTap: () { setState(() { isSelected ? selectedPolicies.remove(id) : selectedPolicies.add(id); totalPolicies = selectedPolicies.length.toString(); }); _calculateTotalCommission(); }, child: Container( height: 38, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( // const Color(0xFFE6F4EF) color: isSelected ? const Color(0xFFF0FAF6) : Colors.white, border: const Border( bottom: BorderSide(color: Color(0xFFf9fafb)), // bottom: BorderSide(color: Color(0xFFEDEDED)), ), ), child: Row( children: [ if (!isEdit) ...[ SizedBox( width: colCheck, child: Checkbox( value: isSelected, onChanged: (v) { setState(() { v == true ? selectedPolicies.add(id) : selectedPolicies.remove(id); totalPolicies = selectedPolicies.length.toString(); }); _calculateTotalCommission(); }, ), ), ], _cell(p["policy_no"], colPolicy), // _cell(p["issued_date"] ?? "-", colDate), _cell(_formatDate(p['issued_date']), colDate), // _cell(p["agent_name"] ?? "-", colAgent), _cell( (p["agent_code"] != null && p["agent_name"] != null) ? '${p["agent_code"]} - ${p["agent_name"]}' : (p["agent_name"] ?? '-'), colAgent, ), _cell(p["customer_name"], colCustomer), _cell("₹${p["premium_amount"]}", colPremium), SizedBox( width: colCommission, child: Row( children: [ const Text( "₹", style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF009B77), ), ), const SizedBox(width: 2), Expanded( child: TextFormField( key: ValueKey( 'commission_${p["policy_id"]}_${p["commission_amount"]}', ), initialValue: (p["commission_amount"] ?? '') .toString(), style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF009B77), ), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), inputFormatters: _commissionInputFormatter, decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric( horizontal: 6, vertical: 6, ), border: OutlineInputBorder(), ), onChanged: (value) { p["commission_amount"] = value.trim(); _calculateTotalCommission(); }, onFieldSubmitted: (value) => _updateInlineCommission(p, value), ), ), ], ), ), ], ), ), ); }, ), ); } double _parseCommissionValue(dynamic value) { if (value == null) return 0; return double.tryParse(value.toString().trim()) ?? 0; } /// Premium from API / table (may include ₹ or commas). double? _parsePremiumAmount(dynamic value) { if (value == null) return null; final cleaned = value .toString() .replaceAll('₹', '') .replaceAll(',', '') .replaceAll(RegExp(r'\s+'), '') .trim(); if (cleaned.isEmpty) return null; return double.tryParse(cleaned); } double _sumUtrAmounts(List> utrs) { double sum = 0; for (final row in utrs) { sum += _parseCommissionValue(row['amount']); } return sum; } bool _amountsMatch(double a, double b, {double epsilon = 0.01}) { return (a - b).abs() < epsilon; } Future _updateInlineCommission(Map row, String value) async { final trimmed = value.trim(); final amount = double.tryParse(trimmed); if (amount == null) { ToastHelper.showWarningToast(context, 'Invalid payout amount'); return; } final int id = int.tryParse((row['id'] ?? row['policy_id'] ?? '').toString()) ?? 0; if (id == 0 || _inlineSavingIds.contains(id)) return; setState(() { _inlineSavingIds.add(id); }); try { final response = await apiService.createUserData( { 'id': id, 'commission_amount': amount, }, 'policy/payoutInilneEditUpdate', ); if (response['status'] == 'success' || response['status'] == 200) { row['commission_amount'] = trimmed; _calculateTotalCommission(); } else { ToastHelper.showErrorToast( context, (response['message'] ?? 'Inline update failed').toString(), ); } } catch (e) { ToastHelper.showErrorToast(context, e.toString()); } finally { if (mounted) { setState(() { _inlineSavingIds.remove(id); }); } } } Widget _cell(String? value, double width) { return SizedBox( width: width, child: Text( value ?? "-", overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11), ), ); } double _calculateTotalCommission() { print("ASD"); print(filteredPolicies); double total = 0.0; for (var p in filteredPolicies) { // Safe policy_id parsing final String idStr = p["policy_id"]?.toString() ?? "0"; final int id = int.tryParse(idStr) ?? 0; if (!selectedPolicies.contains(id)) continue; total += _parseCommissionValue(p["commission_amount"]); } setState(() { totalCommission = total.roundToDouble(); }); // Remove decimal points (.00) return total.roundToDouble(); } Widget buildUTRNumber(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text('UTR Number', style: _textStyle), SizedBox(width: 5), SizedBox( height: 35, width: MediaQuery.of(context).size.width * 0.15, child: TextFormField( controller: controllers['utrNumber']!, inputFormatters: _utrInputFormatter, decoration: commonInputDecoration(hint: 'UTR Number'), ), ), ], ); } InputDecoration commonInputDecoration({required String hint}) { return InputDecoration( hintText: hint, filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 12), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( color: Color(0xFFDDDDDD), // light gray width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( color: Color(0xFFAAAAAA), // darker gray on focus width: 1.2, ), ), ); } static final _textStyle = GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w500, ); String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format } catch (e) { return rawDate; // fallback if parsing fails } } static final double colCheck = 44; double get colPolicy => isEdit ? 150 : 200; double get colDate => isEdit ? 120 : 200; static final double colAgent = 200; static final double colCustomer = 200; static final double colPremium = 200; // static final double colCommission = 200; double get colCommission => isEdit ? 120 : 200; double get _tableMinWidth => (isEdit ? 0 : colCheck) + colPolicy + colDate + colAgent + colCustomer + colPremium + colCommission + 32; }