import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.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 '../../providers/manager_provider.dart'; import 'FormFieldBox.dart'; class PayOutScreen extends ConsumerStatefulWidget { final Map? editItem; const PayOutScreen({super.key, this.editItem}); @override ConsumerState createState() => _PayOutScreenState(); } class _PayOutScreenState extends ConsumerState { // -------------------------- // Invoice Fields // -------------------------- late TextEditingController invoiceNoController; DateTime invoiceDate = DateTime.now(); int? selectedAgentId; int? selectedBrokerID; DateTime? policyTillDate = DateTime.now(); // -------------------------- // Filters // -------------------------- DateTime? fromDate; DateTime? toDate; // -------------------------- // 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; late ApiService apiService; dynamic managerId; dynamic userId; final DateFormat df = DateFormat('dd-MM-yyyy'); @override void initState() { super.initState(); apiService = ApiService(); invoiceNoController = TextEditingController(text: generateInvoiceNumber()); Future.microtask(() async { managerId = ref.watch(managerIdProvider); userId = ref.watch(userIdProvider); if (managerId != null) { await getAgentList(managerId); } await getBroker(); }); // 👉 CHECK EDIT MODE if (widget.editItem != null) { print('editItemID ${widget.editItem}'); final invoiceID = widget.editItem!['id']; _loadEditData(invoiceID); } } void _loadEditData(String invoiceID) async { setState(() => isLoading = true); final response = await apiService.getInvoiceDetails(invoiceID); if (response['status'] == 'success') { final invoice = response['data']['invoice']; final items = List>.from(response['data']['items']); print("Invoice → $invoice"); print("Items → $items"); // ----------------------------- // 1. SET BASIC FIELDS // ----------------------------- setState(() { selectedBrokerID = int.tryParse(invoice['broker_id'].toString()); selectedAgentId = int.tryParse(invoice['agent_id'].toString()); invoiceNoController.text = invoice['invoice_no']; invoiceDate = DateTime.parse(invoice['invoice_date']); if (invoice['till_date'] != "0000-00-00") { policyTillDate = DateTime.parse(invoice['till_date']); } else { policyTillDate = DateTime.now(); } }); print('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(() => isLoading = false); } String formatDate(String date) { final d = DateTime.parse(date); return DateFormat('dd-MM-yyyy').format(d); } // Get Broker List Future getBroker() async { print('getBroker called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Broker'); if (response['status'] == 200) { print('getBroker - ${response['data']}'); setState(() { getBrokerData = List>.from(response['data']); print('API Data - $getBrokerData'); filteredBrokerData = List.from(getBrokerData); print('originalData - $filteredBrokerData'); }); } else { getBrokerData = []; filteredBrokerData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } //Get Agent List Future getAgentList(int id) 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 - $getAgentData'); filteredAgentData = List.from(getAgentData); print('originalData - $filteredAgentData'); }); } else { getAgentData = []; filteredAgentData = []; } } catch (e) { print('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), ); } // Load policies (simulate API) Future loadPolicies() async { try { String formattedTillDate = DateFormat( 'yyyy-MM-dd', ).format(policyTillDate!); final jsondata = { "broker_id": selectedBrokerID, "agent_id": selectedAgentId, "issued_date": formattedTillDate, }; final response = await apiService.getCommissionRateList(jsondata); if (response['status'] == 'success') { print('API Data response - $response'); setState(() { filteredPolicies = List>.from(response['data']); print('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 { filteredPolicies = []; } } catch (e) { print('Exception occurred: $e'); } 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 an agent"); return; } if (selectedPolicies.isEmpty) { ToastHelper.showWarningToast( context, "Please select at least 1 policy", ); return; } // Format dates for API final String formattedInvoiceDate = DateFormat( 'yyyy-MM-dd', ).format(invoiceDate); final String formattedTillDate = DateFormat( 'yyyy-MM-dd', ).format(policyTillDate!); // Total Commission final double totalCommission = filteredPolicies .where((p) => selectedPolicies.contains(int.parse(p["policy_id"]))) .fold(0.0, (sum, p) => sum + double.parse(p["commission_amount"])); // Build items array final List> items = filteredPolicies .where((p) => selectedPolicies.contains(int.parse(p["policy_id"]))) .map( (p) => { "policy_id": int.parse(p["policy_id"]), "policy_no": p["policy_no"], "commission_amount": double.parse(p["commission_amount"]), }, ) .toList(); final jsondata = { "invoice_no": invoiceNoController.text, "invoice_amount": totalCommission, "agent_id": selectedAgentId, "broker_id": selectedBrokerID, "invoice_date": formattedInvoiceDate, "till_date": formattedTillDate, "payout_status": 0, "created_by": userId, "updated_by": userId, "items": items, }; print('Commissions $jsondata'); final response = await apiService.getCreateOrUpdate(jsondata); if (response['status'] == 'success') { setState(() { print('API Data response - $response'); selectedPolicies.clear(); invoiceNoController.clear(); invoiceDate = DateTime.now(); policyTillDate = DateTime.now(); filteredPolicies = []; }); context.go(AppRoutes.invoiceList); } else { filteredPolicies = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } // showDialog( // context: context, // builder: (_) => AlertDialog( // title: const Text("Success"), // content: Text( // "Invoice No: ${invoiceNoController.text}\n" // "Total Commission: ₹$totalComm", // ), // actions: [ // TextButton( // onPressed: () => Navigator.pop(context), // child: const Text("OK"), // ), // ], // ), // ); } void showMessage(String msg) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } @override Widget build(BuildContext context) { final df = DateFormat("dd-MM-yyyy"); return MainLayout( title: "Pay Out", body: SelectionArea( child: Stack( children: [ SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: [ // ---------------------- // Invoice Details // ---------------------- Card( color: Colors.white, // White background clipBehavior: Clip.none, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 1, child: Column( children: [ // ---------- HEADER (CLICK TO COLLAPSE) ---------- ListTile( title: const Text("Invoice Details"), trailing: Icon( invoiceCollapsed ? Icons.expand_more : Icons.expand_less, color: Color(0xFF2F766E), size: 26, ), onTap: () => setState( () => invoiceCollapsed = !invoiceCollapsed, ), ), // ------------------- HR LINE WHEN OPEN ------------------- if (!invoiceCollapsed) const Divider( height: 1, thickness: 1, color: Color(0xFFE0E0E0), ), // ---------- COLLAPSIBLE CONTENT ---------- AnimatedCrossFade( duration: const Duration(milliseconds: 500), crossFadeState: invoiceCollapsed ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: const SizedBox( // FIXED height: 0, width: double.infinity, ), secondChild: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ // ------------------- ROW OF FIELDS ------------------- SizedBox( height: 80, // 👈 ensures bottom alignment works child: Row( crossAxisAlignment: CrossAxisAlignment .center, // 👈 bottom align everything children: [ // INVOICE NO FormFieldBox( label: "Invoice No *", child: TextField( controller: invoiceNoController, readOnly: true, decoration: commonInputDecoration( hint: "Invoice Number", ), ), ), const SizedBox(width: 12), // INVOICE DATE FormFieldBox( label: "Invoice Date *", child: GestureDetector( onTap: () async { final picked = await pickDate( invoiceDate, ); if (picked != null) setState( () => invoiceDate = picked, ); }, child: AbsorbPointer( child: TextField( readOnly: true, decoration: commonInputDecoration( hint: df.format(invoiceDate), ).copyWith( suffixIcon: const Icon( Icons.calendar_today, size: 18, ), ), ), ), ), ), const SizedBox(width: 12), // BROKER FormFieldBox( label: "Select Broker *", child: DropdownButtonFormField( value: selectedBrokerID, decoration: commonInputDecoration( hint: "Select Broker", ), items: filteredBrokerData.map((a) { final int id = int.tryParse( a['id'].toString(), ) ?? 0; return DropdownMenuItem( value: id, child: Text(a['name'].toString()), ); }).toList(), onChanged: (id) => setState( () => selectedBrokerID = id, ), ), ), const SizedBox(width: 12), // AGENT FormFieldBox( label: "Select Agent *", child: DropdownButtonFormField( value: selectedAgentId, decoration: commonInputDecoration( hint: "Select Agent", ), items: filteredAgentData.map((a) { final int id = int.tryParse( a['id'].toString(), ) ?? 0; return DropdownMenuItem( value: id, child: Text(a['name'].toString()), ); }).toList(), onChanged: (id) => setState( () => selectedAgentId = id, ), ), ), const SizedBox(width: 12), // POLICY TILL DATE FormFieldBox( label: "Policy Till Date *", child: GestureDetector( onTap: () async { final picked = await pickDate( policyTillDate!, ); if (picked != null) setState( () => policyTillDate = picked, ); }, child: AbsorbPointer( child: TextField( readOnly: true, decoration: commonInputDecoration( hint: df.format( policyTillDate!, ), ).copyWith( suffixIcon: const Icon( Icons.calendar_today, size: 18, ), ), ), ), ), ), const SizedBox(width: 12), // --- RESET + SEARCH (BOTTOM ALIGNED) --- Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( children: [ // RESET SizedBox( height: 30, width: 30, child: OutlinedButton( style: OutlinedButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: () { setState(() { selectedBrokerID = null; selectedAgentId = null; fromDate = null; toDate = null; policyTillDate = DateTime.now(); filteredPolicies = []; selectedPolicies.clear(); }); }, child: const Icon( Icons.refresh, size: 18, ), ), ), const SizedBox(width: 12), // SEARCH SizedBox( height: 30, width: 30, child: OutlinedButton( style: OutlinedButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: loadPolicies, child: const Icon( Icons.search, size: 18, ), ), ), ], ), ], ), ], ), ), ], ), ), ), ], ), ), const SizedBox(height: 20), // ---------------------- // Policy Selection // ---------------------- Card( color: Colors.white, // White background shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 1, child: Column( children: [ ListTile( // contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), title: const Text("Policy Selection"), // ---------------- RIGHT SIDE (filters + collapse) ---------------- trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // FROM DATE SizedBox( height: 30, width: MediaQuery.of(context).size.width * 0.12, child: GestureDetector( onTap: () async { final date = await pickDate( fromDate ?? DateTime.now(), ); if (date != null) setState(() => fromDate = date); }, child: AbsorbPointer( child: TextField( readOnly: true, decoration: commonInputDecoration( hint: "From Date", ).copyWith( // 👇 THIS is where you set formatted date suffixIcon: const Icon( Icons.calendar_today, size: 18, ), labelText: fromDate == null ? "From Date" : df.format(fromDate!), ), ), ), ), ), const SizedBox(width: 8), // TO DATE SizedBox( height: 30, width: MediaQuery.of(context).size.width * 0.12, child: GestureDetector( onTap: () async { final date = await pickDate( toDate ?? DateTime.now(), ); if (date != null) setState(() => toDate = date); }, child: AbsorbPointer( child: TextField( readOnly: true, decoration: commonInputDecoration( hint: "To Date", ).copyWith( // 👇 THIS is where you set formatted date suffixIcon: const Icon( Icons.calendar_today, size: 18, ), labelText: toDate == null ? "To Date" : df.format(toDate!), ), ), ), ), ), const SizedBox(width: 8), // RESET BUTTON SizedBox( height: 30, width: 30, child: OutlinedButton( style: OutlinedButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: () { setState(() { fromDate = null; toDate = null; }); loadPolicies(); }, child: const Icon(Icons.refresh, size: 18), ), ), const SizedBox(width: 8), // SEARCH BUTTON SizedBox( height: 30, width: 30, child: OutlinedButton( style: OutlinedButton.styleFrom( padding: EdgeInsets.zero, ), onPressed: filterPolicies, child: const Icon(Icons.search, size: 18), ), ), const SizedBox(width: 8), // COLLAPSE ICON GestureDetector( onTap: () => setState( () => policyCollapsed = !policyCollapsed, ), child: Icon( policyCollapsed ? Icons.expand_more : Icons.expand_less, color: Colors.teal, size: 26, ), ), ], ), onTap: () => setState(() => policyCollapsed = !policyCollapsed), ), // ------------------- HR LINE WHEN OPEN ------------------- if (!policyCollapsed) const Divider( height: 1, thickness: 1, color: Color(0xFFE0E0E0), ), AnimatedCrossFade( duration: const Duration(milliseconds: 500), crossFadeState: policyCollapsed ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: Container(), secondChild: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ // ---------------- SELECT ALL ---------------- if (filteredPolicies.isNotEmpty) Container( width: double.infinity, padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 16, ), child: Row( children: [ Checkbox( value: filteredPolicies.isNotEmpty && filteredPolicies.every( (p) => selectedPolicies.contains( int.parse(p["policy_id"]), ), ), onChanged: (v) { setState(() { if (v == true) { selectedPolicies = filteredPolicies .map( (p) => int.parse( p["policy_id"], ), ) .toSet(); } else { selectedPolicies.clear(); } }); }, ), const SizedBox(width: 8), const Text( "Select All Policies", style: TextStyle(fontSize: 16), ), ], ), ), // ---------------- POLICY LIST ---------------- if (filteredPolicies.isEmpty) const Padding( padding: EdgeInsets.all(20), child: Text("No policies found"), ) else ...filteredPolicies.map((p) { // final int id = int.parse(p["policy_id"]); // final bool isSelected = selectedPolicies.contains(id); // ---- SAFE ID PARSING ---- final String policyIdStr = p["policy_id"]?.toString() ?? "0"; final int id = int.tryParse(policyIdStr) ?? 0; final bool isSelected = selectedPolicies .contains(id); // ---- SAFE FIELDS ---- final String policyNo = p["policy_no"]?.toString() ?? "-"; final String customer = p["customer_name"]?.toString() ?? "-"; final String premiumStr = p["premium_amount"]?.toString() ?? "0"; final double premium = double.tryParse(premiumStr) ?? 0; final String commissionStr = p["commission_amount"]?.toString() ?? "0"; final double commission = double.tryParse(commissionStr) ?? 0; return ListTile( tileColor: isSelected ? Colors.teal.shade50 : null, leading: Checkbox( value: isSelected, onChanged: (v) { setState(() { if (v == true) { selectedPolicies.add(id); } else { selectedPolicies.remove(id); } }); }, ), title: Text(policyNo), subtitle: Text( "Customer: ${customer} | " "Premium: ₹${premium}", ), trailing: Text( "₹${commission}", style: const TextStyle( color: Colors.teal, fontWeight: FontWeight.bold, ), ), onTap: () { setState(() { if (isSelected) { selectedPolicies.remove(id); } else { selectedPolicies.add(id); } }); }, ); }).toList(), ], ), ), ), ], ), ), const SizedBox(height: 100), ], ), ), // ------------------------------ // Bottom Summary Bar // ------------------------------ if (selectedPolicies.isNotEmpty) Positioned( left: 0, right: 0, bottom: 0, child: Material( elevation: 8, child: Container( padding: const EdgeInsets.all(16), color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // ---------------- SELECTED COUNT ---------------- Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Selected Policies", style: TextStyle(fontSize: 12), ), const SizedBox(height: 4), Text( selectedPolicies.length.toString(), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), // ---------------- TOTAL COMMISSION ---------------- Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Total Commission", style: TextStyle(fontSize: 12), ), const SizedBox(height: 4), /// NULL-SAFE TOTAL COMMISSION CALCULATION Text( "₹${_calculateTotalCommission()}", style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.teal, ), ), ], ), // ---------------- BUTTONS ---------------- Row( children: [ TextButton( onPressed: () => setState(() => selectedPolicies.clear()), child: const Text("Clear"), ), const SizedBox(width: 10), ElevatedButton( onPressed: saveInvoice, style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, ), child: const Text("Submit"), ), ], ), ], ), ), ), ), ], ), ), ); } double _calculateTotalCommission() { 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; // Safe commission amount final String commStr = p["commission_amount"]?.toString() ?? "0"; final double comm = double.tryParse(commStr) ?? 0; total += comm; } // Remove decimal points (.00) return total.roundToDouble(); } 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, ), ), ); } }