import 'package:flutter/gestures.dart'; import 'package:flutter/material.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 '../../../data/utils/Pagination.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 '../../themes/indicators/export_btn.dart'; import '../../themes/indicators/search_field_theme.dart'; /// Read-only payout invoice + policy lines (opened from payout list “View”). class PayOutDetailsView extends ConsumerStatefulWidget { final Map viewItem; const PayOutDetailsView({super.key, required this.viewItem}); @override ConsumerState createState() => _PayOutDetailsViewState(); } class _PayOutDetailsViewState extends ConsumerState { final TextEditingController _searchStaffController = TextEditingController(); List> masterPolicies = []; List> filteredPolicies = []; bool isLoading = false; late ApiService apiService; dynamic managerId; dynamic totalPolicies = '0'; dynamic totalCommission = '0'; int _rowsPerPage = 10; int _currentPage = 1; @override void initState() { super.initState(); apiService = ApiService(); Future.microtask(() { managerId = ref.read(managerIdProvider); _loadInvoicePolicies(); }); } @override void dispose() { _searchStaffController.dispose(); super.dispose(); } Future _loadInvoicePolicies() async { final invoiceId = widget.viewItem['id']; if (invoiceId == null) { setState(() { filteredPolicies = []; masterPolicies = []; }); return; } setState(() => isLoading = true); try { final jsondata = { 'invoice_id': invoiceId, 'manager_id': managerId, }; final response = await apiService.getViewInvoiceList(jsondata); if (response['status'] == 'success') { final payload = response['data']; List> items = []; Map? invoiceMap; if (payload is Map) { final m = Map.from(payload); invoiceMap = m['invoice'] is Map ? Map.from(m['invoice'] as Map) : null; final rawItems = m['items']; if (rawItems is List) { items = rawItems .map((e) => Map.from(e as Map)) .toList(); } } else if (payload is List) { items = List>.from(payload); } num commissionSum = 0; for (final row in items) { commissionSum += num.tryParse(row['commission_amount']?.toString() ?? '0') ?? 0; } final totPolicy = (invoiceMap?['total_policies'] ?? items.length) .toString(); final totalCommiss = invoiceMap?['total_commission'] ?? commissionSum; setState(() { totalPolicies = totPolicy; totalCommission = totalCommiss; masterPolicies = items; filteredPolicies = List>.from(items); _currentPage = 1; }); } else { setState(() { masterPolicies = []; filteredPolicies = []; }); } } catch (_) { setState(() { masterPolicies = []; filteredPolicies = []; }); } finally { if (mounted) setState(() => isLoading = false); } } void filterPolicyData(String query) { final q = query.trim(); setState(() { _currentPage = 1; if (q.isEmpty) { filteredPolicies = List.from(masterPolicies); return; } filteredPolicies = masterPolicies.where((item) { return (item['policy_no'] ?? '').toString().contains(q) || (item['vehicle_type'] ?? '').toString().contains(q) || (item['customer_name'] ?? '').toString().contains(q) || (item['agent_name'] ?? '').toString().contains(q) || (item['insurer_name'] ?? '').toString().contains(q) || (item['premium_amount'] ?? '').toString().contains(q) || (item['commission_amount'] ?? '').toString().contains(q) || _formatDate(item['issued_date']?.toString() ?? '').contains(q); }).toList(); }); } String _displayText(dynamic value) { if (value == null) return '-'; final text = value.toString().trim(); if (text.isEmpty || text.toLowerCase() == 'null') return '-'; return text; } String _invoiceDateLabel() { final ui = widget.viewItem['invoice_date_ui_format']; if (ui != null && ui.toString().trim().isNotEmpty) { return ui.toString(); } final raw = widget.viewItem['invoice_date']; if (raw == null) return '-'; try { return DateFormat('dd-MM-yyyy').format(DateTime.parse(raw.toString())); } catch (_) { return raw.toString(); } } String _formatDate(String? rawDate) { if (rawDate == null || rawDate.isEmpty) return '-'; try { return DateFormat('dd-MM-yyyy').format(DateTime.parse(rawDate)); } catch (_) { return rawDate; } } String _amountText(dynamic value) { final text = _displayText(value); if (text == '-') return '-'; return text.replaceAll('₹', '').trim(); } Widget _headerText( String text, int flex, { TextAlign align = TextAlign.left, }) { return Expanded( flex: flex, child: Text( text, textAlign: align, style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w500, color: Colors.blueGrey, letterSpacing: 0.5, ), ), ); } Widget _cell( String? value, int flex, { TextAlign align = TextAlign.left, Color? color, FontWeight? fontWeight, }) { final displayValue = (value == null || value.trim().isEmpty || value.toLowerCase() == 'undefined' || value.toLowerCase() == 'null') ? '-' : value; return Expanded( flex: flex, child: Text( displayValue, textAlign: align, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, color: color, fontWeight: fontWeight, ), ), ); } Widget _policyTableHeader() { return Container( height: 30, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: const BoxDecoration( color: Color(0xFFf9fafb), border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))), ), child: Row( children: [ _headerText('S.NO', 5, align: TextAlign.center), _headerText('POLICY NO', 10), _headerText('VEHICLE TYPE', 10), _headerText('POLICY DATE', 10, align: TextAlign.center), _headerText('PARTNER', 20), _headerText('CUSTOMER', 10), _headerText('PREMIUM (₹)', 11, align: TextAlign.right), _headerText('PAYOUT (₹)', 11, align: TextAlign.right), ], ), ); } Widget _policyRow(Map p, int index) { return Container( height: 38, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: const BoxDecoration( color: Colors.white, border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))), ), child: Row( children: [ _cell('${index + 1}', 5, align: TextAlign.center), _cell(p['policy_no']?.toString(), 10), _cell(p['vehicle_type']?.toString(), 10), _cell(_formatDate(p['issued_date']?.toString()), 10, align: TextAlign.center), _cell( (p['agent_code'] != null && p['agent_name'] != null) ? '${p['agent_code']} - ${p['agent_name']}' : (p['agent_name'] ?? '-').toString(), 20, ), _cell(p['customer_name']?.toString(), 10), _cell( _amountText(p['premium_amount']), 11, align: TextAlign.right, ), _cell( _amountText(p['commission_amount']), 11, align: TextAlign.right, color: const Color(0xFF009B77), fontWeight: FontWeight.w600, ), ], ), ); } int get _totalPages { if (filteredPolicies.isEmpty) return 1; return (filteredPolicies.length / _rowsPerPage).ceil(); } List> get _paginatedPolicies { if (filteredPolicies.isEmpty) return const >[]; final start = (_currentPage - 1) * _rowsPerPage; final end = (start + _rowsPerPage).clamp(0, filteredPolicies.length); return filteredPolicies.sublist(start, end); } void _goToPage(int page) { if (page < 1 || page > _totalPages) return; setState(() => _currentPage = page); } List> get _exportRows { return filteredPolicies.map((p) { return { 'sno': 0, 'policy_no': p['policy_no'], 'vehicle_type': p['vehicle_type'], 'policy_date': _formatDate(p['issued_date']?.toString()), 'partner': (p['agent_code'] != null && p['agent_name'] != null) ? '${p['agent_code']} - ${p['agent_name']}' : (p['agent_name'] ?? '-').toString(), 'customer': p['customer_name'], 'premium': _amountText(p['premium_amount']), 'payout': _amountText(p['commission_amount']), }; }).toList(); } @override Widget build(BuildContext context) { final statusRaw = widget.viewItem['payout_status']; final statusInt = int.tryParse(statusRaw?.toString() ?? '0') ?? 0; final isPending = statusInt == 1; final invoiceNo = _displayText(widget.viewItem['invoice_no']); return MainLayout( title: 'Pay Out', body: isLoading ? Container( color: Colors.white, height: MediaQuery.of(context).size.height * 0.7, child: const Center(child: CircularProgressIndicator()), ) : SelectionArea( child: Container( color: Colors.white, padding: const EdgeInsets.all(8), // margin: EdgeInsets.symmetric( // horizontal: MediaQuery.of(context).size.width * 0.1, // ), child: Column( children: [ SizedBox( height: 40, width: MediaQuery.of(context).size.width, child: Row( crossAxisAlignment: CrossAxisAlignment.center, 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), Text( 'View Payout Details', style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, ), ), const Spacer(), Text( invoiceNo, style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w500, color: const Color(0xFF374151), ), ), const SizedBox(width: 10), Text( '(${_invoiceDateLabel()})', style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w300, color: const Color(0xFF6B7280), ), ), const SizedBox(width: 10), Text( 'Status: ${isPending ? 'Pending' : 'Completed'}', style: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w300, color: const Color(0xFF6B7280), ), ), const Spacer(), ThemedSearchField( hintText: 'Search', onChanged: filterPolicyData, controller: _searchStaffController, backgroundColor: const Color(0xFFFFFFFF), txtHeight: 30, txtwidth: ResponsiveLayout.isMobile(context) ? MediaQuery.of(context).size.width * 0.7 : MediaQuery.of(context).size.width * 0.15, ), const SizedBox(width: 10), ExportBtn( sheetName: 'Payout Details', fileName: 'payout_details_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}', data: _exportRows, displayHeaders: const [ 'S.NO', 'POLICY NO', 'VEHICLE TYPE', 'POLICY DATE', 'PARTNER', 'CUSTOMER', 'PREMIUM (₹)', 'PAYOUT (₹)', ], keys: const [ 'sno', 'policy_no', 'vehicle_type', 'policy_date', 'partner', 'customer', 'premium', 'payout', ], ), ], ), ), Expanded( child: Card( color: Colors.white, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, child: ScrollConfiguration( behavior: const MaterialScrollBehavior().copyWith( dragDevices: { PointerDeviceKind.mouse, PointerDeviceKind.touch, PointerDeviceKind.trackpad, }, ), child: LayoutBuilder( builder: (context, constraints) { final rows = _paginatedPolicies; final startIdx = (filteredPolicies.isEmpty) ? 0 : ((_currentPage - 1) * _rowsPerPage); return Column( children: [ _policyTableHeader(), Expanded( child: filteredPolicies.isEmpty ? const Center( child: Text( 'No policies in this payout', style: TextStyle( fontSize: 12, color: Colors.blueGrey, ), ), ) : ListView.builder( itemCount: rows.length, itemBuilder: (context, index) { return _policyRow( rows[index], startIdx + index, ); }, ), ), ], ); }, ), ), ), ), Container( padding: const EdgeInsets.all(8), color: const Color(0xFFf9fafb), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text( '$totalPolicies', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.blueGrey, ), ), const SizedBox(width: 4), Text( 'Policies', 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), Text( '₹ $totalCommission', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.teal, ), ), ], ), ), if (filteredPolicies.isNotEmpty) Padding( padding: const EdgeInsets.fromLTRB(8, 4, 8, 2), child: Align( alignment: Alignment.centerRight, child: PaginationControls( currentPage: _currentPage, itemsPerPage: _rowsPerPage, totalItems: filteredPolicies.length, onPageChanged: (page) => _goToPage(page), onItemsPerPageChanged: (items) { setState(() { _rowsPerPage = items; _currentPage = 1; }); }, ), ), ), ], ), ), ), ); } }