import 'package:dropdown_search/dropdown_search.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:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:nhance_partner/presentation/screens/staff/policy/policyPdf.dart'; import 'package:pdf_render/pdf_render_widgets.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import '../../../../core/config/env.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/utils/toastNotification.dart'; import '../../../providers/manager_provider.dart'; import '../../../themes/indicators/input_field_decoration.dart'; import 'loadNetworkPdfBytes.dart'; import 'dart:typed_data'; import 'package:flutter/services.dart'; final digitsOnlyFormatter = [FilteringTextInputFormatter.digitsOnly]; final decimalFormatter = [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')), ]; class policyValidation extends ConsumerStatefulWidget { final dynamic item; const policyValidation({Key? key, this.item}) : super(key: key); @override ConsumerState createState() => _policyValidationState(); } class _policyValidationState extends ConsumerState { // --- Api / state late ApiService apiService; bool isLoading = false; bool isLoadingFile = false; bool isLoadingVehicleType = false; bool isLoadingVehicleTypeForCommission = false; bool isLoadingFuelType = false; bool isLoadingBroker = false; bool isLoadingPaymentMode = false; bool isLoadingInsurancePlan = false; bool isCommissionManuallyEditable = false; dynamic managerId; dynamic userId; String? _cachedPdfUrl; String? pdfUrl; // full file path from API bool highlightEnqBroker = false; bool highlightDocBroker = false; String? enqBroker; // Stores the NAME of the enquiry broker final ValueNotifier brokerMismatchNotifier = ValueNotifier(false); final ValueNotifier isDeletingNotifier = ValueNotifier(false); // --- Controllers (all editable fields) final List controllerKeys = [ // Policy 'policyNumber', 'insuredName', 'premiumAmount', 'remarks', // Dates 'issuedDate', 'startDate', 'endDate', // Tax 'tp', 'od', 'pa', 'cgst', 'sgst', 'igst', 'payment_mode' // Vehicle 'rcNo', 'rtoStateCode', 'rtoCityCode', 'weight', 'fuelType', 'dateOfRegistration', 'yearOfManufacture', 'engineNo', 'chassisNo', 'make', 'model', // 'cubic_capacity', // Ensure this matches your dataDetails call 'cubicCapacity', 'vehicleType', // Broker 'broker_name', 'commission_amount', // Enquiry 'enquiry_broker_id_for_commission', 'enquiry_reg_no_for_commission', 'enquiry_vehicle_type_id_for_commission', //find Policy 'insurance_plan_type_id', // agent code and agent name in single. 'agent_code_and_name', ]; late Map controllers; String? selectedInsuranceId; String? selectedAgentRentionRate; String? selectedManagerRentionRate; String? selectedInsurancePlanTypeId; List getVehicleTypeData = []; List filteredVehicleTypeData = []; List getFuelTypeData = []; List filteredFuelTypeData = []; List> getBrokerData = []; List> filteredBrokerData = []; List> getInsurancPlanTypeData = []; List> filteredInsurancPlanTypeData = []; List> getVehicleTypeForEnquiryData = []; List> filteredVehicleDataForEnquiryData = []; List> getPaymentModeData = []; List> filteredPaymentModeData = []; final GlobalKey _pdfViewerKey = GlobalKey(); final _formKey = GlobalKey(); String? selectedVehicleTypeForEnquiryID; String? selectedVehicleTypeForEnquiryVAL; String? selectedVehicleType; String? selectedPaymentMode; String? selectedFuelType; String? selectedBroker; String? selectedInsurerName; String? selectedInsurerShortName; String? selectedPartnerCodeAndName; final GlobalKey>> dropDownKey = GlobalKey>>(); final GlobalKey>> dropDownKeyFuel = GlobalKey>>(); final GlobalKey>> dropDownKeyBroker = GlobalKey>>(); final GlobalKey>> dropDownKeyVehicleTypeForEnquiry = GlobalKey>>(); final GlobalKey>> dropDownKeyInsurancPlanType = GlobalKey>>(); final GlobalKey>> dropDownKeyPaymentMode = GlobalKey>>(); bool _hasLoadedInitialData = false; Map dataDetails() { return { "rc_no": controllers["rcNo"]?.text.trim().isNotEmpty == true ? controllers["rcNo"]?.text : controllers["enquiry_reg_no_for_commission"]?.text, "insured_name": controllers["insuredName"]?.text, "issued_date": controllers["issuedDate"]?.text, "start_date": controllers["startDate"]?.text, "end_date": controllers["endDate"]?.text, "premium_amount": controllers["premiumAmount"]?.text, "policy_number": controllers["policyNumber"]?.text, "tp": controllers["tp"]?.text?.isEmpty ?? true ? null : controllers["tp"]?.text, "od": controllers["od"]?.text?.isEmpty ?? true ? null : controllers["od"]?.text, "pa": controllers["pa"]?.text?.isEmpty ?? true ? null : controllers["pa"]?.text, "cgst": controllers["cgst"]?.text?.isEmpty ?? true ? null : controllers["cgst"]?.text, "sgst": controllers["sgst"]?.text?.isEmpty ?? true ? null : controllers["sgst"]?.text, "igst": controllers["igst"]?.text?.isEmpty ?? true ? null : controllers["igst"]?.text, "rto_state_code": controllers["rtoStateCode"]?.text?.isEmpty ?? true ? null : controllers["rtoStateCode"]?.text, "rto_city_code": controllers["rtoCityCode"]?.text?.isEmpty ?? true ? null : controllers["rtoCityCode"]?.text, "weight": controllers["weight"]?.text?.isEmpty ?? true ? null : controllers["weight"]?.text, "fuel_type": controllers["fuelType"]?.text?.isEmpty ?? true ? null : controllers["fuelType"]?.text, "date_of_registration": controllers["dateOfRegistration"]?.text?.isEmpty ?? true ? null : controllers["dateOfRegistration"]?.text, // "date_of_registration": (controllers["dateOfRegistration"]?.text.isNotEmpty ?? false) // ? DateFormat('yyyy-MM-dd').format( DateFormat('dd-MM-yyyy').parse(controllers["dateOfRegistration"]!.text),) // : null, "year_of_manufacture": controllers["yearOfManufacture"]?.text.isEmpty ?? true ? null : controllers["yearOfManufacture"]?.text, "engine_no": controllers["engineNo"]?.text?.isEmpty ?? true ? null : controllers["engineNo"]?.text, "chassis_no": controllers["chassisNo"]?.text?.isEmpty ?? true ? null : controllers["chassisNo"]?.text, "make": controllers["make"]?.text?.isEmpty ?? true ? null : controllers["make"]?.text, "model": controllers["model"]?.text?.isEmpty ?? true ? null : controllers["model"]?.text, "cubic_capacity": controllers["cubicCapacity"]?.text?.isEmpty ?? true ? null : controllers["cubicCapacity"]?.text, // "vehicle_type": controllers["vehicleType"]?.text?.isEmpty ?? true // ? selectedVehicleTypeForEnquiryVAL // : controllers["vehicleType"]?.text, "vehicle_type": controllers["vehicleType"]?.text.trim().isNotEmpty == true ? controllers["vehicleType"]?.text : null, "broker_name": controllers["broker_name"]?.text?.isEmpty ?? true ? null : controllers["broker_name"]?.text, // "enquiry_broker_id_for_commission": controllers["enquiry_broker_id_for_commission"]?.text?.isEmpty ?? true // ? null // : controllers["enquiry_broker_id_for_commission"]?.text, "enquiry_broker_id_for_commission": controllers["enquiry_broker_id_for_commission"]?.text.trim().isNotEmpty == true ? controllers["enquiry_broker_id_for_commission"]?.text : selectedBroker, "enquiry_reg_no_for_commission": controllers["enquiry_reg_no_for_commission"]?.text?.isEmpty ?? true ? null : controllers["enquiry_reg_no_for_commission"]?.text, // "enquiry_vehicle_type_id_for_commission": controllers["enquiry_vehicle_type_id_for_commission"]?.text?.isEmpty ?? true // ? null // : controllers["enquiry_vehicle_type_id_for_commission"]?.text, "enquiry_vehicle_type_id_for_commission": controllers["enquiry_vehicle_type_id_for_commission"] ?.text .trim() .isNotEmpty == true ? controllers["enquiry_vehicle_type_id_for_commission"]?.text : selectedVehicleTypeForEnquiryID, "commission_amount": controllers["commission_amount"]?.text?.isEmpty ?? true ? null : controllers["commission_amount"]?.text, "payment_mode": controllers["payment_mode"]?.text.trim().isNotEmpty == true ? controllers["payment_mode"]?.text : selectedPaymentMode, "insurance_plan_type_id": selectedInsurancePlanTypeId, // If there is no data means "agent_code_and_name" : selectedPartnerCodeAndName, }; } Map buildCommissionPayload() { final data = dataDetails(); final policyId = widget.item?['policy_id']; final pm = (data["payment_mode"] == null || data["payment_mode"] == 0 || data["payment_mode"] == '0' || data["payment_mode"].toString().isEmpty) ? selectedPaymentMode : data["payment_mode"]; return { "vehicle_type": data["vehicle_type"] ?? selectedVehicleType, "insurance_plan_type_id": selectedInsurancePlanTypeId, "premium_amount": data["premium_amount"], "od": data["od"], "tp": data["tp"], "cubic_capacity": data["cubic_capacity"], "weight": data["weight"], "make": data["make"], "model": data["model"], "year_of_manufacture": data["year_of_manufacture"], "vehicle_age": '', "date_of_registration": data["date_of_registration"], "fuel_type": data["fuel_type"], "rto_state_code": data["rto_state_code"], "rto_city_code": data["rto_city_code"], "issued_date": data["issued_date"], "insurer_id": selectedInsuranceId, "agent_retention_rate": selectedAgentRentionRate, "manager_retention_rate": selectedManagerRentionRate, "id": policyId, "updated_by": userId, "enquiry_broker_id_for_commission": selectedBroker, "enquiry_reg_no_for_commission": data["enquiry_reg_no_for_commission"], "enquiry_vehicle_type_id_for_commission": selectedVehicleTypeForEnquiryID, "payment_mode": pm, "agent_code_and_name" : selectedPartnerCodeAndName }; } @override void initState() { super.initState(); apiService = ApiService(); // instantiate controllers with defaults controllers = { for (final key in controllerKeys) key: TextEditingController(), }; update(); _loadInitialData(); // Set the initial enqBroker name from the fetched data list if (selectedBroker != null) { final item = filteredBrokerData.firstWhere( (element) => element['id'].toString() == selectedBroker, orElse: () => {}, ); enqBroker = item['name']?.toString(); } // Check immediately after data is loaded WidgetsBinding.instance.addPostFrameCallback((_) => _validateBrokerMatch()); } Future _loadInitialData() async { print('_loadInitialData'); try { managerId = ref.read(managerIdProvider); } catch (e) { managerId = null; } try { userId = ref.read(userIdProvider); } catch (e) { userId = null; } await Future.wait([ getFindPolicy(), getVehicleType(), getFuelType(), getPolicyFilePath(), getBroker(), getInsurancPlanType(), getPaymentMode(), getEnquiryVehicleTypeForCommission() ]); } @override void dispose() { for (final controller in controllers.values) { controller.dispose(); } super.dispose(); } void update() { final item = widget.item as Map? ?? {}; // Prefill from API / passed data item.forEach((key, value) { if (controllers.containsKey(key)) { controllers[key]!.text = value?.toString() ?? ''; } }); } Future getBroker() async { print('getBroker called'); setState(() { isLoadingBroker = 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(() { isLoadingBroker = false; }); } } Future getPaymentMode() async { setState(() => isLoadingPaymentMode = true); try { final response = await apiService.fetchMasterDropDown('PaymentMode'); if (response['status'] == 200 && response['data'] != null) { final allData = List>.from(response['data']); final activePaymentMode = allData.where((item) => item['is_active'] == "1").toList(); setState(() { getPaymentModeData = activePaymentMode; filteredPaymentModeData = List.from(getPaymentModeData); // ✅ IMPORTANT: rebind selected value AFTER list is ready if (selectedPaymentMode != null && selectedPaymentMode!.isNotEmpty && selectedPaymentMode != '0') { // nothing else needed – rebuild is enough } }); } else { // setState(() { getPaymentModeData.clear(); filteredPaymentModeData.clear(); // }); } } catch (e) { print('Exception occurred: $e'); } finally { setState(() => isLoadingPaymentMode = false); } } Future getInsurancPlanType() async { print('getInsurancPlanType called'); setState(() { isLoadingInsurancePlan = true; }); try { final response = await apiService.fetchMasterDropDown('InsuranceType'); if (response['status'] == 200) { print('getPlanType - ${response['data']}'); setState(() { getInsurancPlanTypeData = List>.from(response['data']); print('API Data - $getInsurancPlanTypeData'); filteredInsurancPlanTypeData = List>.from(getInsurancPlanTypeData); print('originalData - $filteredInsurancPlanTypeData'); }); } else { getInsurancPlanTypeData = []; filteredInsurancPlanTypeData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoadingInsurancePlan = false; }); } } Future getEnquiryVehicleTypeForCommission() async { setState(() => isLoadingVehicleTypeForCommission = true); try { final response = await apiService.fetchMasterDropDown('vehicleType','dropdown'); if (response['status'] == 200 && response['data'] != null) { final allData = List>.from(response['data']); final activeVehicles = allData.where((item) => item['is_active'] == "1").toList(); setState(() { // 2. Assign the full objects getVehicleTypeForEnquiryData = allData; filteredVehicleDataForEnquiryData = List.from(activeVehicles); // 3. Extract just the names for your String list getVehicleTypeData = activeVehicles .map((item) => item['vehicle_type'].toString()) .toList(); filteredVehicleTypeData = List.from(getVehicleTypeData); }); } else { getVehicleTypeForEnquiryData.clear(); filteredVehicleDataForEnquiryData.clear(); getVehicleTypeData.clear(); filteredVehicleTypeData.clear(); } } catch (e) { print('Exception occurred: $e'); } finally { setState(() => isLoadingVehicleTypeForCommission = false); } } // Future getVehicleType() async { // print('getvehicleType called'); // setState(() => isLoadingVehicleType = true); // // try { // final response = await apiService.fetchPolicyMasterDropDown( // 'vehicleType', // ); // // if (response['status'] == 'success' && response['data'] != null) { // print('Vehicle Types: ${response['data']}'); // setState(() { // getVehicleTypeData = List.from(response['data']); // filteredVehicleTypeData = List.from(getVehicleTypeData); // }); // // print('Vehicle Types: $getVehicleTypeData'); // } else { // getVehicleTypeData.clear(); // filteredVehicleTypeData.clear(); // } // } catch (e) { // print('Exception occurred: $e'); // } finally { // setState(() => isLoadingVehicleType = false); // } // } Future getVehicleType() async { setState(() => isLoadingVehicleType = true); try { final response = await apiService.fetchPolicyMasterDropDown('vehicleType'); if (response['status'] == 'success' && response['data'] != null) { final list = List.from(response['data']); setState(() { getVehicleTypeData = list; filteredVehicleTypeData = List.from(list); // ✅ REBIND AFTER LIST IS READY final apiVehicleType = controllers['vehicleType']?.text; if (apiVehicleType != null && list.contains(apiVehicleType)) { controllers['vehicleType']!.text = apiVehicleType; } }); } else { getVehicleTypeData.clear(); filteredVehicleTypeData.clear(); } } catch (e) { print('Exception occurred: $e'); } finally { setState(() => isLoadingVehicleType = false); } } // Future getFuelType() async { // print('getfuelType called'); // setState(() => isLoadingFuelType = true); // // try { // final response = await apiService.fetchPolicyMasterDropDown('fuelType'); // // if (response['status'] == 'success' && response['data'] != null) { // print('fuelType Types: ${response['data']}'); // setState(() { // getFuelTypeData = List.from(response['data']); // filteredFuelTypeData = List.from(getFuelTypeData); // }); // // print('fuelType Types: $getFuelTypeData'); // } else { // getFuelTypeData.clear(); // filteredFuelTypeData.clear(); // } // } catch (e) { // print('Exception occurred: $e'); // } finally { // setState(() => isLoadingFuelType = false); // } // } Future getFuelType() async { setState(() => isLoadingFuelType = true); try { final response = await apiService.fetchPolicyMasterDropDown('fuelType'); if (response['status'] == 'success' && response['data'] != null) { final list = List.from(response['data']); setState(() { getFuelTypeData = list; filteredFuelTypeData = List.from(list); // ✅ REBIND AFTER LIST IS READY final apiFuel = controllers['fuelType']?.text; if (apiFuel != null && list.contains(apiFuel)) { controllers['fuelType']!.text = apiFuel; } }); } else { getFuelTypeData.clear(); filteredFuelTypeData.clear(); } } catch (e) { print('Exception occurred: $e'); } finally { setState(() => isLoadingFuelType = false); } } Future getPolicyFilePath() async { _pdfViewerKey.currentState?.openBookmarkView(); final policyId = widget.item?['policy_id']; if (policyId == null) { debugPrint('policyValidation: policy id is null, skipping API call'); return; } if (!mounted) return; setState(() => isLoadingFile = true); try { final url = Uri.parse( '${Env.apiUrl}policy/PolicyFilePath?policy_id=$policyId&file_type=policy_pdf', ); setState(() { pdfUrl = "$url"; _cachedPdfUrl = pdfUrl; }); print("FINAL PDF URL -> $pdfUrl"); } catch (e, st) { print('Exception in getPolicyFilePath: $e\n$st'); } finally { if (!mounted) return; setState(() => isLoadingFile = false); } } String fixInvalidDate(dynamic value) { if (value == null) return ''; final str = value.toString(); // Check for your invalid date patterns if (str.contains('--0001') || str == '30-11--0001' || str == '0000-00-00') { return ''; } return str; } String safeText(dynamic value, {String fallback = ''}) { if (value == null) return fallback; final text = value.toString().trim(); return text.isEmpty ? fallback : text; } Future getFindPolicy() async { final policyId = widget.item?['policy_id']; if (policyId == null) { debugPrint('policyValidation: policy id is null, skipping API call'); return; } if (!mounted) return; setState(() => isLoading = true); try { final response = await apiService.findPolicyApi(policyId); debugPrint('findPolicyApi response: $response'); if (response != null && response['status'] == 'success') { final data = (response['data'] ?? {}) as Map; debugPrint('findPolicyApi response1: '); if (!mounted) return; debugPrint('findPolicyApi response2:'); setState(() { selectedInsurerShortName = safeText(data['insurer_short_name']); selectedInsurerName = safeText(data['insurer_name']); controllers["policyNumber"]?.text = safeText(data['policy_number']); controllers["insuredName"]!.text = safeText(data['insured_name']); controllers["premiumAmount"]!.text = safeText(data['premium_amount']); // controllers["issuedDate"]!.text = safeText(data['issued_date']); // controllers["startDate"]!.text = safeText(data['start_date']); // controllers["endDate"]!.text = safeText(data['end_date']); controllers["issuedDate"]?.text = fixInvalidDate(data['issued_date']); controllers["startDate"]?.text = fixInvalidDate(data['start_date']); controllers["endDate"]?.text = fixInvalidDate(data['end_date']); print('check for date'); print(controllers["issuedDate"]?.text); controllers["tp"]!.text = safeText(data['tp']); controllers["od"]!.text = safeText(data['od']); controllers["pa"]!.text = safeText(data['pa']); controllers["cgst"]!.text = safeText(data['cgst']); controllers["sgst"]!.text = safeText(data['sgst']); controllers["igst"]!.text = safeText(data['igst']); controllers["rtoStateCode"]!.text = safeText(data['rto_state_code']); controllers["rtoCityCode"]!.text = safeText(data['rto_city_code']); controllers["weight"]!.text = safeText(data['weight']); // controllers["dateOfRegistration"]!.text = safeText( // data['date_of_registration'], // ); controllers["dateOfRegistration"]!.text = apiDateToUiDate(data['date_of_registration']); // selectedFuelType = safeText(data['fuel_type']); controllers['fuelType']?.text = safeText(data['fuel_type']); controllers["vehicleType"]?.text = safeText(data['vehicle_type']); selectedPaymentMode = safeText(data['payment_mode_id']); controllers['payment_mode']?.text = selectedPaymentMode ?? ''; controllers["engineNo"]!.text = safeText(data['engine_no']); controllers["chassisNo"]!.text = safeText(data['chassis_no']); controllers["make"]!.text = safeText(data['make']); controllers["model"]!.text = safeText(data['model']); controllers["cubicCapacity"]!.text = safeText(data['cubic_capacity']); controllers["yearOfManufacture"]!.text = safeText( data['year_of_manufacture'], ); selectedInsuranceId = safeText(data['insurer_id']); selectedAgentRentionRate = safeText(data['agent_retention_rate']); selectedManagerRentionRate = safeText(data['manager_retention_rate']); controllers["commission_amount"]?.text = safeText( data['commission_amount'], ); // FIX: Removed the invalid "data['key'] ? ..." syntax selectedBroker = data['enquiry_broker_id_for_commission']?.toString() ?? ''; selectedVehicleTypeForEnquiryID = data['enquiry_vehicle_type_id_for_commission']?.toString() ?? ''; controllers["enquiry_reg_no_for_commission"]?.text = safeText(data['enquiry_reg_no_for_commission']); // controllers["rcNo"]!.text = safeText(data['rc_no']); // 1. Set the State variable (This makes the Dropdown show the correct item) selectedInsurancePlanTypeId = data['insurance_plan_type_id']?.toString(); // 2. Set the Controller (This ensures dataDetails() can pick it up if needed) controllers["insurance_plan_type_id"]?.text = selectedInsurancePlanTypeId ?? ''; selectedPartnerCodeAndName = safeText(data['agent_code']) + '-' + safeText(data['agent_name']); controllers["agent_code_and_name"]?.text = selectedPartnerCodeAndName ?? ''; }); debugPrint('findPolicyApi response3:'); } else { debugPrint( 'findPolicyApi returned error: ${response?['message'] ?? 'unknown'}', ); } } catch (e, st) { debugPrint('Exception in getFindPolicy: $e\n$st'); } finally { if (!mounted) return; setState(() => isLoading = false); } } String apiDateToUiDate(dynamic value) { if (value == null) return ''; final str = value.toString().trim(); if (str.isEmpty) return ''; try { // API → yyyy-MM-dd final date = DateFormat('yyyy-MM-dd').parse(str); // UI → dd-MM-yyyy return DateFormat('dd-MM-yyyy').format(date); } catch (_) { return ''; } } String apiToUiDate(String? value) { if (value == null || value.isEmpty) return ''; if (value.contains('--')) return ''; // API already sends dd-MM-yyyy return value; // return DateFormat('dd-MM-yyyy') // .format(DateFormat('yyyy-MM-dd').parse(value)); } Future _pickDate( TextEditingController controller, BuildContext context, ) async { DateTime initial = DateTime.now(); try { if (controller.text.isNotEmpty) { // try parse dd-MM-yyyy final parts = controller.text.split('-'); if (parts.length == 3) { final d = int.tryParse(parts[0]) ?? initial.day; final m = int.tryParse(parts[1]) ?? initial.month; final y = int.tryParse(parts[2]) ?? initial.year; initial = DateTime(y, m, d); } } } catch (_) {} final picked = await showDatePicker( context: context, initialDate: initial, firstDate: DateTime(1900), lastDate: DateTime(2100), ); if (picked != null) { controller.text = "${picked.day.toString().padLeft(2, '0')}-${picked.month.toString().padLeft(2, '0')}-${picked.year}"; } } Future _save() async { print('SAVE'); final policyId = widget.item?['policy_id']; if (policyId == null) { ToastHelper.showWarningToast(context, 'Missing policy id'); return; } if (!_formKey.currentState!.validate()) { ToastHelper.showWarningToast(context, "Please fill all required fields"); return; } final commissionAmount = controllers["commission_amount"]?.text.trim() ?? ''; if (widget.item?['is_data_accuracy_checked']?.toString() == '0' && commissionAmount.isEmpty) { ToastHelper.showWarningToast( context, "Please calculate commission amount", ); return; } // if (widget.item?['is_data_accuracy_checked'] == "1") { // ToastHelper.showWarningToast(context, "Data already confirmed"); // return; // } // 1. Force a final check _validateBrokerMatch(); // 2. Check the notifier value instead of the old booleans if (brokerMismatchNotifier.value) { final String enqName = (enqBroker ?? 'Not Selected'); final String docName = (controllers['broker_name']?.text ?? 'Empty'); // await _confirmBrokerChange( // context, // enquiryBroker: enqName, // documentBroker: docName, // ); return; // 🛑 STOP: Does not proceed to API call } // Show the dialog and wait for the result final bool confirmed = await _confirmChange(context); // Logic: If 'Cancel' was pressed (false), stop execution here if (!confirmed) { debugPrint("User cancelled submission"); return; } final payload = {"id": policyId, ...dataDetails(), "updated_by": userId}; if (!mounted) return; setState(() => isLoading = true); try { debugPrint('FINAL SAVE PAYLOAD => $payload'); final result = await apiService.updatePolicyApi(payload); debugPrint('updatePolicy result => $result'); if (result != null && result['status'] == 'success') { if (!mounted) return; setState(() => isLoading = false); context.pop(); ToastHelper.showSuccessToast(context, "Policy updated successfully"); WidgetsBinding.instance.addPostFrameCallback((_) { // Now safe to do navigation + provider updates final container = ProviderScope.containerOf(context); container.read(policyDataAcurancyRefreshProvider.notifier).state = true; }); } else { if (!mounted) return; setState(() => isLoading = false); ToastHelper.showErrorToast( context, result?['message']?.toString() ?? 'Error updating policy', ); } } catch (e, st) { debugPrint('Exception in updatePolicy: $e\n$st'); if (!mounted) return; setState(() => isLoading = false); ToastHelper.showWarningToast(context, 'Exception: $e'); } } DateTime? parseDate(String? value) { if (value == null || value.isEmpty) return null; try { return DateFormat('dd-MM-yyyy').parse(value); // change format if required } catch (_) { return null; } } void _validateBrokerMatch() { final String enqName = (enqBroker ?? '').trim().toLowerCase(); final String docName = (controllers['broker_name']?.text ?? '').trim().toLowerCase(); // setState(() { // if (enqName.isNotEmpty && docName.isNotEmpty && enqName != docName) { // highlightEnqBroker = true; // highlightDocBroker = true; // } else { // // If they match OR one is empty, remove the red highlight // highlightEnqBroker = false; // highlightDocBroker = false; // } // }); // Update the notifier instead of a boolean + setState brokerMismatchNotifier.value = (enqName.isNotEmpty && docName.isNotEmpty && enqName != docName); } Future _confirmBrokerChange( BuildContext context, { required String enquiryBroker, required String documentBroker, }) async { return await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.orange), SizedBox(width: 8), Text("Broker Mismatch", style: GoogleFonts.poppins(fontWeight: FontWeight.bold)), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("The brokers do not match:", style: GoogleFonts.poppins(fontSize: 13)), const SizedBox(height: 16), _dialogBrokerRow("Enquiry Broker:", enquiryBroker), const SizedBox(height: 12), _dialogBrokerRow("Document Broker:", documentBroker), // const SizedBox(height: 20), // Text("Do you want to proceed with the update?", // style: GoogleFonts.poppins(fontWeight: FontWeight.w500, fontSize: 13)), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text("OK", style: TextStyle(color: Colors.grey[700], fontWeight: FontWeight.bold)), ), // ElevatedButton( // style: ElevatedButton.styleFrom( // backgroundColor: const Color(0xFF2E7D6E), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), // ), // onPressed: () => Navigator.pop(context, true), // child: const Text("PROCEED", style: TextStyle(color: Colors.white)), // ), ], ), ) ?? false; } // Future _confirmChange(BuildContext context) async { // return await showDialog( // context: context, // barrierDismissible: false, // builder: (context) => AlertDialog( // contentPadding: EdgeInsets.zero, // Allows inner containers to touch edges // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), // content: Column( // mainAxisSize: MainAxisSize.min, // children: [ // // --- Top Section: Content with Green Sidebar --- // Container( // // decoration: const BoxDecoration( // // color: Colors.white, // // borderRadius: BorderRadius.vertical(top: Radius.circular(16)), // // border: Border( // // left: BorderSide(color: Color(0xFF00C853), width: 6), // Green accent // // ), // // ), // padding: const EdgeInsets.all(24), // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Row( // children: [ // // Icon Container // Container( // padding: const EdgeInsets.all(6), // decoration: BoxDecoration( // color: const Color(0xFFE8F5E9), // borderRadius: BorderRadius.circular(8), // ), // child: const Icon(Icons.check_circle_outline, // color: Color(0xFF00C853), size: 28), // ), // const SizedBox(width: 12), // Text( // "Action Required", // style: GoogleFonts.poppins( // fontWeight: FontWeight.bold, // fontSize: 18, // color: Colors.black87, // ), // ), // ], // ), // const SizedBox(height: 20), // _bulletItem("Please review your changes before submitting."), // const SizedBox(height: 12), // _bulletItem("Changes cannot be made after submission."), // ], // ), // ), // // // --- Bottom Section: Buttons with Gray Background --- // Container( // padding: const EdgeInsets.all(16), // decoration: const BoxDecoration( // color: Color(0xFFE5EAE9), // Light gray background for footer // borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)), // ), // child: Row( // children: [ // Expanded( // child: ElevatedButton( // style: ElevatedButton.styleFrom( // backgroundColor: const Color(0xFFF5F5F5), // foregroundColor: Colors.grey[700], // elevation: 0, // padding: const EdgeInsets.symmetric(vertical: 14), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // ), // onPressed: () => Navigator.pop(context, false), // child: const Text("Cancel", style: TextStyle(fontWeight: FontWeight.bold)), // ), // ), // const SizedBox(width: 12), // Expanded( // child: ElevatedButton( // style: ElevatedButton.styleFrom( // backgroundColor: const Color(0xFF13B156), // foregroundColor: Colors.white, // elevation: 0, // padding: const EdgeInsets.symmetric(vertical: 14), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // ), // onPressed: () => Navigator.pop(context, true), // child: const Text("PROCEED", style: TextStyle(fontWeight: FontWeight.bold)), // ), // ), // ], // ), // ), // ], // ), // ), // ) ?? false; // } // // // Helper widget for bullets // Widget _bulletItem(String text) { // return Row( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // const Padding( // padding: EdgeInsets.only(top: 8), // child: Icon(Icons.circle, size: 6, color: Colors.black), // ), // const SizedBox(width: 12), // Expanded( // child: Text( // text, // style: GoogleFonts.poppins(fontSize: 14, color: Colors.black87), // ), // ), // ], // ); // } Future _confirmChange(BuildContext context) async { return await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( // --- These two lines ensure the background is exactly white --- backgroundColor: Colors.white, surfaceTintColor: Colors.transparent, // -------------------------------------------------------------- contentPadding: EdgeInsets.zero, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), content: Column( mainAxisSize: MainAxisSize.min, children: [ // --- Top Section: Content with Green Sidebar --- Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), // border: Border( // left: BorderSide(color: Color(0xFF00C853), width: 6), // Green accent // ), ), padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ // Icon Container Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: const Color(0xFFE8F5E9), borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.warning_amber_rounded, color: Color(0xFF00C853), size: 28, ), ), const SizedBox(width: 12), Text( "Action Required", style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.black87, ), ), ], ), const SizedBox(height: 20), _bulletItem("Please review your changes before submitting."), const SizedBox(height: 12), _bulletItem("Changes cannot be made after submission."), ], ), ), // --- Bottom Section: Buttons with Gray Background --- Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, // Gray footer background borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)), ), child: Row( children: [ Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFF5F5F5), foregroundColor: Colors.grey[700], elevation: 0, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () => Navigator.pop(context, false), child: const Text("Cancel", style: TextStyle(fontWeight: FontWeight.bold)), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF13B156), foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () => Navigator.pop(context, true), child: const Text("PROCEED", style: TextStyle(fontWeight: FontWeight.bold)), ), ), ], ), ), ], ), ), ) ?? false; } // Helper widget for the bullet points Widget _bulletItem(String text) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 6), child: Icon(Icons.circle, size: 6, color: Colors.black54), ), const SizedBox(width: 10), Expanded( child: Text( text, style: const TextStyle(color: Colors.black54, fontSize: 14), ), ), ], ); } Widget _dialogBrokerRow(String label, String value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: GoogleFonts.poppins(fontSize: 11, color: Colors.grey[600])), Text(value, style: GoogleFonts.poppins( fontSize: 14, color: Colors.red[700], fontWeight: FontWeight.bold ) ), ], ); } Future _fetchCommision() async { print('_fetchCommision IN'); final policyId = widget.item?['policy_id']; if (policyId == null) { ToastHelper.showWarningToast(context, 'Missing policy id'); return; } final startDate = parseDate(controllers["startDate"]?.text); final endDate = parseDate(controllers["endDate"]?.text); if (startDate != null && endDate != null && startDate.isAfter(endDate)) { ToastHelper.showErrorToast( context, 'Start Date cannot be greater than End Date', ); return; } if (!_formKey.currentState!.validate()) { ToastHelper.showWarningToast(context, "Please fill all required fields"); return; } // if (widget.item?['is_data_accuracy_checked'] == "1") { // ToastHelper.showWarningToast(context, "Data already confirmed"); // return; // } // final payload = { // "id": policyId, // 'insurance_plan_type': controllers['insurance_plan_type'], // "updated_by": userId, // ...dataDetails(), // // "insurance_plan_type": // }; // print('_fetchCommision1'); // setState(() { // isLoading = true; // payload['insurance_plan_type'] = selectedInsurancPlanType; // payload['insurer_id'] = selectedInsuranceId; // payload['id'] = policyId; // payload['updated_by'] = userId; // print('payload2 - ${payload['insurance_plan_type']}'); // print('payload - $payload'); // }); // print('_fetchCommision2'); // 👉 CRITICAL FIX: Set loading to true BEFORE the API call starts setState(() { isLoading = true; }); try { final payload = buildCommissionPayload(); debugPrint('Payload in fetchCommission: $payload'); final result = await apiService.calculateCommissionRequest(payload); debugPrint('Result in fetchCommission: $result'); if (result != null && result['status'] == 'success') { if (!mounted) return; // setState(() => isLoading = false); // context.pop(); // Extract commission_amount from response final commissionAmount = result['data']?['commission_amount']; setState(() { isLoading = false; // Stop loading if (commissionAmount != null) { // Update controller so the Confirm button becomes active controllers["commission_amount"]?.text = double.parse( commissionAmount.toString(), ).toStringAsFixed(2); } }); ToastHelper.showSuccessToast( context, "Commission calculated successfully", ); // WidgetsBinding.instance.addPostFrameCallback((_) { // // Now safe to do navigation + provider updates // final container = ProviderScope.containerOf(context); // container.read(policyDataAcurancyRefreshProvider.notifier).state = // true; // }); } else { if (!mounted) return; setState(() => isLoading = false); ToastHelper.showErrorToast( context, result?['message']?.toString() ?? 'Error calculating commission', ); } } catch (e, st) { debugPrint('Exception in fetchCommission: $e\n$st'); if (!mounted) return; setState(() => isLoading = false); ToastHelper.showWarningToast(context, 'Exception: $e'); } print('_fetchCommision OUT'); } bool get _showChangeCommissionAction { final String status = (widget.item?['is_data_accuracy_checked']?.toString() ?? '').trim(); return status == '0' || status == '1'; } bool get _isDataAccuracyChecked { final String status = (widget.item?['is_data_accuracy_checked']?.toString() ?? '').trim(); return status == '1'; } Future _updateCommissionOnly() async { final dynamic policyId = widget.item?['policy_id'] ?? widget.item?['id']; final String commissionText = controllers['commission_amount']?.text.trim() ?? ''; if (policyId == null) { ToastHelper.showWarningToast(context, 'Missing policy id'); return; } final double? commission = double.tryParse(commissionText); if (commission == null) { ToastHelper.showWarningToast(context, 'Please enter valid commission amount'); return; } setState(() => isLoading = true); try { final payload = { 'id': policyId, 'commission_amount': commission.toStringAsFixed(2), 'updated_by': userId, }; final result = await apiService.updatePolicyCommissionApi(payload); if (result['status'] == 'success') { if (!mounted) return; setState(() => isCommissionManuallyEditable = false); ToastHelper.showSuccessToast(context, 'Commission updated successfully'); } else { ToastHelper.showErrorToast( context, result['data']?.toString() ?? result['message']?.toString() ?? 'Failed to update commission', ); } } catch (e) { ToastHelper.showErrorToast(context, 'Exception: $e'); } finally { if (mounted) setState(() => isLoading = false); } } Widget buildVehicleType(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Product (as per Document)", style: GoogleFonts.poppins(fontWeight: FontWeight.w500, fontSize: 11), ), const SizedBox(height: 6), DropdownSearch( key: dropDownKey, selectedItem: controllers['vehicleType']!.text.isNotEmpty ? controllers['vehicleType']!.text : null, items: (filter, infiniteScrollProps) { return filteredVehicleTypeData; }, itemAsString: (val) => val, // 👈 String directly validator: (val) { if (val == null || val.isEmpty) { return ""; // 👈 triggers error border, no text } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Product", ).copyWith( filled: true, fillColor: Colors.white, isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( // color: Color(0xFFE2E8F0), width: 0.6, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( // color: Color(0xFFE2E8F0), width: 0.6, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), contentPadding: const EdgeInsets.symmetric( horizontal: 8, vertical: 10, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: const BoxConstraints(maxHeight: 250), menuProps: const MenuProps(backgroundColor: Colors.white), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: const EdgeInsets.all(6), filled: true, fillColor: Colors.white, hintText: "Search Product...", hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: Color(0xFFE2E8F0), width: 1.5), ), ), ), ), onChanged: (val) { if (val != null) { // setState(() { // selectedVehicleType = val; controllers['vehicleType']?.text = val; // }); // setState(() {}); print("Selected Vehicle Type: $val"); } }, ), ], ); } Widget buildFuelType(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Fuel Type *", style: GoogleFonts.poppins(fontWeight: FontWeight.w500, fontSize: 11), ), const SizedBox(height: 6), DropdownSearch( key: dropDownKeyFuel, selectedItem: controllers['fuelType']!.text.isNotEmpty ? controllers['fuelType']!.text : null, items: (filter, infiniteScrollProps) { return filteredFuelTypeData; }, itemAsString: (val) => val, // 👈 String directly // validator: (val) { // if (val == null || val.isEmpty) { // return ""; // 👈 triggers error border, no text // } // return null; // }, validator: (val) { if (val == null || val.isEmpty) { return ""; // Triggers red border for empty } // NEW: Also return error if it's a junk value if (val.startsWith('UN_') || val.toLowerCase().contains('undefined')) { return "Invalid Selection"; } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Fuel Type", ).copyWith( filled: true, fillColor: Colors.white, isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( // color: Color(0xFFE2E8F0), width: 0.6, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide( // color: Color(0xFFE2E8F0), width: 0.6, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), contentPadding: const EdgeInsets.symmetric( horizontal: 8, vertical: 10, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: const BoxConstraints(maxHeight: 250), menuProps: const MenuProps(backgroundColor: Colors.white), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: const EdgeInsets.all(6), filled: true, fillColor: Colors.white, hintText: "Search Fuel Type...", hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: Color(0xFFE2E8F0), width: 1.5), ), ), ), ), onChanged: (val) { if (val != null) { // setState(() { // selectedFuelType = val; controllers['fuelType']?.text = val; // }); // setState(() {}); print("Selected Fuel Type: $val"); } }, ), ], ); } Widget buildBrokerAsPerEnquiry(BuildContext context, String EnqID) { // 1. Find the currently selected broker object from your list final Map? selectedBrokers = selectedBroker != null ? filteredBrokerData.firstWhere( (item) => item['id'].toString() == selectedBroker, orElse: () => {}, ) : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // HEADER SECTION: Label + Info Icon Row( mainAxisSize: MainAxisSize.min, children: [ Text( "Broker (as per Enquiry)", style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(width: 6), Tooltip( message: 'View Audit History', child: InkWell( onTap: () { showDialog( context: context, barrierDismissible: false, builder: (_) => NewAuditHistoryModal( enquiryId: EnqID, ), ); }, child: const Icon( Icons.info_outline, size: 14, color: Color(0xFF2E7D6E), ), ), ), ], ), const SizedBox(height: 6), // DROPDOWN SECTION: Searchable + Red Border Logic ValueListenableBuilder( valueListenable: brokerMismatchNotifier, // Listens to the mismatch flag builder: (context, isMismatched, child) { return Container( decoration: BoxDecoration( // The border turns red instantly if there is a mismatch border: Border.all( color: isMismatched ? Colors.red : Colors.transparent, width: 1.5, ), borderRadius: BorderRadius.circular(6), ), child: DropdownSearch>( key: dropDownKeyBroker, // Define how to compare broker maps (Critical to prevent errors) compareFn: (item1, item2) => item1['id'].toString() == item2['id'].toString(), // Items to show items: (filter, _) => filteredBrokerData, // Display the broker name in the selection itemAsString: (item) => item['name'].toString(), selectedItem: (selectedBrokers != null && selectedBrokers.isNotEmpty) ? selectedBrokers : null, // SEARCH CONFIGURATION popupProps: PopupProps.menu( showSearchBox: true, // Enables the search bar searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search broker name...", prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), ), fit: FlexFit.loose, ), onChanged: (val) { if (val != null) { // Update data variables selectedBroker = val['id'].toString(); enqBroker = val['name'].toString(); controllers['enquiry_broker_id_for_commission']?.text = val['id'].toString(); // Trigger the highlight check WITHOUT calling setState _validateBrokerMatch(); } }, validator: (val) => val == null ? "" : null, ), ); }, ) ], ); } Widget buildBOASPerEnquiry(BuildContext context, String EnqID) { final Map? selectedBrokers = selectedBroker != null ? filteredBrokerData.firstWhere( (item) => item['id'].toString() == selectedBroker, orElse: () => {}, ) : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Text( "Broker (as per Enquiry)", style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(width: 6), Tooltip( message: 'View Audit History', child: InkWell( onTap: () { showDialog( context: context, barrierDismissible: false, builder: (_) => NewAuditHistoryModal( enquiryId: EnqID, // Note: Ensure this class is defined correctly as discussed ), ); }, child: const Icon( Icons.info_outline, size: 14, color: Color(0xFF2E7D6E), ), ), ), ], ), const SizedBox(height: 6), Container( decoration: BoxDecoration( border: Border.all( color: highlightEnqBroker ? Colors.red : Colors.transparent, width: 1.5, ), borderRadius: BorderRadius.circular(6), ), child: DropdownSearch>( key: dropDownKeyBroker, items: (filter, _) => filteredBrokerData, selectedItem: selectedBrokers != null && selectedBrokers.isNotEmpty ? selectedBrokers : null, itemAsString: (item) => item['name'].toString(), compareFn: (item1, item2) => item1['id'].toString() == item2['id'].toString(), onChanged: (val) { if (val != null) { setState(() { selectedBroker = val['id'].toString(); enqBroker = val['name'].toString(); controllers['enquiry_broker_id_for_commission']?.text = val['id'].toString(); }); _validateBrokerMatch(); // 👈 Check mismatch immediately } }, validator: (val) { if (val == null) return ""; return null; }, ), ) ], ); } Widget buildVehicleTypeAsPerEnquiry(BuildContext context) { final Map? selectedVehicle = selectedVehicleTypeForEnquiryID != null ? filteredVehicleDataForEnquiryData.firstWhere( (item) => item['id'].toString() == selectedVehicleTypeForEnquiryID, orElse: () => {}, ) : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Product (as per Enquiry) *", style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(height: 6), DropdownSearch>( key: dropDownKeyVehicleTypeForEnquiry, items: (filter, _) => filteredVehicleDataForEnquiryData, // disabledItemFn: (item) => item['is_active'] == "0", compareFn: (a, b) => a['id'].toString() == b['id'].toString(), selectedItem: selectedVehicle != null && selectedVehicle.isNotEmpty ? selectedVehicle : null, itemAsString: (item) => item['vehicle_type'].toString(), // 2. Styling for the items in the list popupProps: PopupProps.menu( itemBuilder: (context, item, isSelected, isHovered) { final bool isActive = item['is_active'] == "1"; return ListTile( title: Text( item['vehicle_type'].toString(), style: TextStyle( // Gray out text if inactive color: isActive ? Colors.black : Colors.grey, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), // Add a "Disabled" subtitle for clarity subtitle: isActive ? null : const Text("Inactive", style: TextStyle(fontSize: 10, color: Colors.red)), ); }, ), onChanged: (val) { if (val != null) { selectedVehicleTypeForEnquiryID = val['id'].toString(); // ✅ store ID selectedVehicleTypeForEnquiryVAL = val['vehicle_type'].toString(); controllers['enquiry_vehicle_type_id_for_commission']?.text = val['id'].toString(); } }, validator: (val) { if (val == null) return ""; return null; }, ), ], ); } Widget buildPaymentMode(BuildContext context) { final Map? selectedPM = selectedPaymentMode != null ? filteredPaymentModeData.firstWhere( (item) => item['id'].toString() == selectedPaymentMode, orElse: () => {}, ) : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Payment Mode *", style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(height: 6), DropdownSearch>( key: dropDownKeyPaymentMode, items: (filter, _) => filteredPaymentModeData, // disabledItemFn: (item) => item['is_active'] == "0", compareFn: (a, b) => a['id'].toString() == b['id'].toString(), selectedItem: selectedPM != null && selectedPM.isNotEmpty ? selectedPM : null, itemAsString: (item) => item['value'].toString(), // 2. Styling for the items in the list popupProps: PopupProps.menu( itemBuilder: (context, item, isSelected, isHovered) { final bool isActive = item['is_active'] == "1"; return ListTile( title: Text( item['value'].toString(), style: TextStyle( // Gray out text if inactive color: isActive ? Colors.black : Colors.grey, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), // Add a "Disabled" subtitle for clarity subtitle: isActive ? null : const Text("Inactive", style: TextStyle(fontSize: 10, color: Colors.red)), ); }, ), onChanged: (val) { if (val != null) { selectedPaymentMode = val['id'].toString(); // ✅ store ID // ✅ WRITE INTO CONTROLLER (CRITICAL) controllers['payment_mode']?.text = selectedPaymentMode!; } }, validator: (val) { if (val == null) return ""; return null; }, ), ], ); } Widget buildInsurancPlanType(BuildContext context) { final Map? selectedIPT = selectedInsurancePlanTypeId != null ? filteredInsurancPlanTypeData.firstWhere( (item) => item['id'].toString() == selectedInsurancePlanTypeId, orElse: () => {}, ) : null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Insurance Plan Type", style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(height: 6), DropdownSearch>( key: dropDownKeyInsurancPlanType, items: (filter, _) => filteredInsurancPlanTypeData, // disabledItemFn: (item) => item['is_active'] == "0", compareFn: (a, b) => a['id'].toString() == b['id'].toString(), selectedItem: selectedIPT != null && selectedIPT.isNotEmpty ? selectedIPT : null, itemAsString: (item) => item['insurance_plan_type'].toString(), // 2. Styling for the items in the list popupProps: PopupProps.menu( itemBuilder: (context, item, isSelected, isHovered) { final bool isActive = item['is_active'] == "1"; return ListTile( title: Text( item['insurance_plan_type'].toString(), style: TextStyle( // Gray out text if inactive color: isActive ? Colors.black : Colors.grey, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), // Add a "Disabled" subtitle for clarity subtitle: isActive ? null : const Text("Inactive", style: TextStyle(fontSize: 10, color: Colors.red)), ); }, ), onChanged: (val) { if (val != null) { selectedInsurancePlanTypeId = val['id'].toString(); // ✅ store ID // ✅ WRITE INTO CONTROLLER (CRITICAL) controllers['insurance_plan_type_id']?.text = selectedInsurancePlanTypeId!; } }, validator: (val) { if (val == null) return ""; return null; }, ), ], ); } Widget _sectionHeader(String title, {IconData icon = Icons.info , String? rightTitle,}) { return Container( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), decoration: BoxDecoration( color: Color(0xFFE3F9F8), borderRadius: BorderRadius.circular(6), ), child: Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: const BoxDecoration( color: Color(0xFF2E7D6E), shape: BoxShape.circle, ), child: Icon(icon, size: 12, color: Colors.white), ), const SizedBox(width: 10), Text( title, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), // Push right title to end const Spacer(), // Right title (optional) if (rightTitle != null && rightTitle.isNotEmpty) Text( rightTitle, style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 12, ), ), ], ), ); } Widget _buildInput( String label, TextEditingController controller, { TextInputType keyboardType = TextInputType.text, bool readOnly = false, VoidCallback? onTap, bool required = false, String? hintText, List? inputFormatters, // 👈 ADD THIS ValueChanged? onChanged, // 👈 ADD THIS String? rightLabel, // 👈 ADD THIS Widget? infoIcon, // 👈 ADD THIS bool highlight = false, // 👈 ADD THIS }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Text( // label, // style: GoogleFonts.poppins(fontWeight: FontWeight.w500, fontSize: 11), // ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: GoogleFonts.poppins(fontWeight: FontWeight.w500, fontSize: 11), ), // if (required) // const Text( // ' *', // style: TextStyle(color: Colors.red, fontSize: 11), // ), if (infoIcon != null) ...[ const SizedBox(width: 6), infoIcon, ], const Spacer(), if (rightLabel != null) Text( rightLabel, style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 11, color: Colors.grey[700], ), ), ], ), const SizedBox(height: 6), TextFormField( controller: controller, keyboardType: keyboardType, readOnly: readOnly, onTap: onTap, onChanged: onChanged, inputFormatters: inputFormatters, // 👈 USE HERE validator: (val) { // if (val == null || val.isEmpty) { // return ""; // 👈 triggers error border, no text // } if (!required) { return null; // ✅ skip validation entirely } if (val == null || val.isEmpty) { return ''; // show red border only when required } return null; }, decoration: InputDecoration( border: OutlineInputBorder(borderRadius: BorderRadius.circular(6), borderSide: BorderSide( color: highlight ? Colors.red : Colors.grey, width: highlight ? 1.6 : 1, ), ), // Inside _buildInput decoration: enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: BorderSide( color: highlight ? Colors.red : Colors.grey.shade400, width: highlight ? 1.6 : 1, ), ), isDense: true, hintText: hintText, hintStyle: hintText == null ? null : GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), // alignment fix helperText: ' ', helperStyle: const TextStyle(height: 1), errorMaxLines: 2, errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: const BorderSide(color: Colors.red), ), contentPadding: const EdgeInsets.all(10), ), ), ], ); } @override Widget build(BuildContext context) { // Ensure global AppHeader remains visible by pushing content down by 64px return SafeArea( child: Scaffold( backgroundColor: Color(0xffD9EBE8), // ⭐ FIXED BUTTON FOOTER bottomNavigationBar: Container( padding: const EdgeInsets.all(16), color: const Color(0xFFF7FBFA), child: Row( mainAxisAlignment: MainAxisAlignment.end, // 👉 move button to right children: [ // ElevatedButton( // // onPressed: widget.item?['is_data_accuracy_checked'] == '1' // // onPressed: // // widget.item?['is_data_accuracy_checked'] != '1' || // // (controllers['commission_amount']?.text.isEmpty ?? true) // // ? null // // : _save, // onPressed: (widget.item?['is_data_accuracy_checked']?.toString() == '0' && // (controllers['commission_amount']?.text.trim().isNotEmpty ?? false)) // ? _save // : null, // // onPressed: // // (widget.item?['is_data_accuracy_checked']?.toString() == // // '0' && // // ((double.tryParse( // // controllers["commission_amount"]?.text ?? // // '0', // // ) ?? // // 0) // // .floor() >= // // 1)) // // ? _save // // : null, // style: ElevatedButton.styleFrom( // backgroundColor: const Color(0xFF2E7D6E), // foregroundColor: Colors.white, // padding: const EdgeInsets.symmetric( // vertical: 10, // horizontal: 20, // 👉 smaller width // ), // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // ), // child: Text( // 'Confirm Data Accuracy ', // // '(${controllers["commission_amount"]?.text ?? '0'} | ' // // '${widget.item?['is_data_accuracy_checked'] ?? '0'})', // style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), // ), // ), ValueListenableBuilder( valueListenable: controllers['commission_amount']!, builder: (context, value, _) { final bool isEnabled = widget.item?['is_data_accuracy_checked']?.toString() == '0' && value.text.trim().isNotEmpty; return ElevatedButton( onPressed: isEnabled ? _save : null, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2E7D6E), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 20, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text( 'Confirm Data Accuracy', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), ); }, ), ], ), ), body: Container( // margin: const EdgeInsets.only(top: 64), // <-- keeps popup under global AppHeader width: double.infinity, // height: MediaQuery.of(context).size.height - 64, color: const Color(0xFFF7FBFA), child: Column( children: [ // In-body header Container( height: 56, color: Color(0xFFE3F9F8), padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ IconButton( icon: const Icon(Icons.file_open), onPressed: () => context.pop(), ), const Expanded( child: Text( 'Insurance Policy Review', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), ), if (isLoading) const SizedBox( width: 12, height: 12, child: CircularProgressIndicator(strokeWidth: 2), ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.close), onPressed: () => context.pop(), ), ], ), ), // Body: left PDF preview + right grouped form Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ // LEFT - PDF placeholder Expanded( flex: 1, child: Container( margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( 'Original Policy Document', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Expanded( child: PolicyPdfViewer( pdfUrl: pdfUrl, ), // 👈 Use separate widget ), ], ), ), ), // RIGHT - Grouped Form Expanded( flex: 1, child: SingleChildScrollView( child: Form( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, // autovalidateMode: AutovalidateMode.disabled, child: Card( elevation: 2, color: Colors.white, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Policy Details Card( elevation: 5, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _sectionHeader( 'Policy Details', icon: Icons.policy, rightTitle: selectedInsurerShortName?.isNotEmpty == true ? 'INSURER - ${selectedInsurerShortName}' : '', ), SizedBox(height: 10), Row( children: [ Expanded( child: _buildInput( 'Policy Number *', controllers['policyNumber']!, required: true, // rightLabel: selectedInsurerName, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Insured Name *', controllers['insuredName']!, required: true, ), ), ], ), Row( children: [ Expanded( child: _buildInput( 'Issued Date *', controllers['issuedDate']!, readOnly: true, required: true, onTap: () => _pickDate( controllers['issuedDate']!, context, ), ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Start Date *', controllers['startDate']!, readOnly: true, required: true, onTap: () => _pickDate( controllers['startDate']!, context, ), ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'End Date *', controllers['endDate']!, readOnly: true, required: true, onTap: () => _pickDate( controllers['endDate']!, context, ), ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Premium Amount *', controllers['premiumAmount']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), ], ), Row( children: [ Expanded( child: buildVehicleTypeAsPerEnquiry( context, ), ), ], ), Row( children: [ Expanded( child: _buildInput( 'Partner Code And Name', controllers['agent_code_and_name']!, required : false, // rightLabel: selectedInsurerName, ), ) ], ) ], ), ), ), const SizedBox(height: 7), // Tax Details Card( elevation: 5, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _sectionHeader( 'Tax / Cover Details *', icon: Icons.attach_money, ), SizedBox(height: 10), Row( children: [ Expanded( child: _buildInput( 'TP *', controllers['tp']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'OD *', controllers['od']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'PA *', controllers['pa']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), ], ), Row( children: [ Expanded( child: _buildInput( 'CGST *', controllers['cgst']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'SGST *', controllers['sgst']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'IGST *', controllers['igst']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), ], ), Row( children:[ Expanded( child: buildPaymentMode( context, ), ),] ), ], ), ), ), const SizedBox(height: 7), // Vehicle Details Card( elevation: 5, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _sectionHeader( 'Vehicle Details', icon: Icons.directions_car, ), SizedBox(height: 10), Row( children: [ Expanded( child: _buildInput( 'Reg No *', controllers['enquiry_reg_no_for_commission']!, required: true, ), ), ] ), Row( children: [ Expanded( child: _buildInput( 'RTO State Name *', controllers['rtoStateCode']!, required: true, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'RTO City Code *', controllers['rtoCityCode']!, required: true, keyboardType: TextInputType.number, inputFormatters: digitsOnlyFormatter, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Weight *', controllers['weight']!, required: false, keyboardType: TextInputType.number, inputFormatters: digitsOnlyFormatter, ), ), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: buildFuelType(context), // child: _buildInput( // 'Fuel Type', // controllers['fuelType']!, // required: true, // ), ), const SizedBox(width: 8), // Expanded( // child: _buildInput( // 'Date of Registration *', // controllers['dateOfRegistration']!, // required: true, // readOnly: true, // onTap: () => _pickDate( // controllers['dateOfRegistration']!, // context, // ), // ), // ), Expanded( child: _buildInput( 'Date of Registration *', controllers['dateOfRegistration']!, required: true, keyboardType: TextInputType.number, hintText: 'DD-MM-YYYY', inputFormatters: [ LengthLimitingTextInputFormatter(10), // DD-MM-YYYY DateInputFormatter(), ], ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Year of Manufacture *', controllers['yearOfManufacture']!, required: true, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter .digitsOnly, // only numbers LengthLimitingTextInputFormatter( 4, ), // max 4 digits ], ), ), ], ), Row( children: [ Expanded( child: _buildInput( 'Engine No *', controllers['engineNo']!, required: true, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Chassis No *', controllers['chassisNo']!, required: true, ), ), const SizedBox(width: 8), Expanded( child: Expanded( child: _buildInput( 'Make *', controllers['make']!, required: true, ), ), ), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: _buildInput( 'Model *', controllers['model']!, required: true, ), ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Cubic Capacity *', controllers['cubicCapacity']!, required: true, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, ), ), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( // child: _buildInput( // 'Vehicle Type', // controllers['vehicleType']!, // required: true, // ), child: buildVehicleType( context, ), ), ], ), ], ), ), ), const SizedBox(height: 7), // Broker Details Card( elevation: 5, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _sectionHeader( 'Broker / Commission', icon: Icons.person, ), SizedBox(height: 10), Row( children: [ Expanded(child:buildBrokerAsPerEnquiry(context,widget.item['id'].toString())), const SizedBox(width: 8), Expanded(child:buildInsurancPlanType(context)), ], ), Row( children: [ Expanded( child: ValueListenableBuilder( valueListenable: brokerMismatchNotifier, builder: (context, isMismatched, child) { return _buildInput( 'Broker (as per Document) *', controllers['broker_name']!, required: true, highlight: isMismatched, // 👈 Passes true/false to your _buildInput onChanged: (val) { _validateBrokerMatch(); }, ); }, ), ), // Expanded( // child: _buildInput( // 'Broker (as per Document)', // controllers['broker_name']!, // required: true, // infoIcon: Tooltip( // message: 'View Audit History', // child: InkWell( // onTap: () { // showDialog( // context: context, // barrierDismissible: false, // builder: (_) => NewAuditHistoryModal( // policyId: '878', // ), // ); // }, // child: const Icon( // Icons.info_outline, // size: 14, // color: Color(0xFF2E7D6E), // ), // ), // ), // ), // ), const SizedBox(width: 8), Expanded( child: _buildInput( 'Commission Amount *', controllers['commission_amount']!, required: false, readOnly: !isCommissionManuallyEditable, keyboardType: TextInputType.numberWithOptions( decimal: true, ), inputFormatters: decimalFormatter, // onChanged: (_) => setState(() {}), ), ), ], ), Row( mainAxisAlignment: MainAxisAlignment .end, // Pushes children to the right children: [ if (_showChangeCommissionAction) TextButton( onPressed: isLoading ? null : () async { /* * First click: allow manual edit. * Next click while editable: submit to updatePolicyCommission API. */ if (!isCommissionManuallyEditable) { setState(() { isCommissionManuallyEditable = true; }); return; } await _updateCommissionOnly(); }, child: Text( isCommissionManuallyEditable ? 'Update Commission' : 'Change Commission', style: TextStyle( color: isLoading ? Colors.grey : const Color(0xFF2E7D6E), fontWeight: FontWeight.bold, ), ), ), TextButton( onPressed: (_isDataAccuracyChecked || isLoading) ? null // Disable if already checked or currently loading : _fetchCommision, child: Text( isLoading ? "Calculating..." : "Calculate", style: TextStyle( color: (_isDataAccuracyChecked || isLoading) ? Colors.grey : const Color( 0xFF2E7D6E, ), fontWeight: FontWeight.bold, ), ), ), ], ), ], ), ), ), const SizedBox( height: 20, ), // spacing above fixed button ], ), ), ), ), ), ), ], ), ), ), ], ), ), ), ); } static final _textStyle = GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, ); } class DateInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { // Backspace allowed if (newValue.text.length < oldValue.text.length) { return newValue; } String digits = newValue.text.replaceAll(RegExp(r'[^0-9]'), ''); if (digits.length > 8) { digits = digits.substring(0, 8); } String formatted = ''; if (digits.isNotEmpty) { formatted += digits.substring(0, digits.length.clamp(0, 2)); } if (digits.length >= 2) { formatted += '-'; formatted += digits.substring(2, digits.length.clamp(2, 4)); } if (digits.length >= 4) { formatted += '-'; formatted += digits.substring(4); } return TextEditingValue( text: formatted, selection: TextSelection.collapsed(offset: formatted.length), ); } } // 1. THE WIDGET CLASS class NewAuditHistoryModal extends ConsumerStatefulWidget { final String enquiryId; const NewAuditHistoryModal({super.key, required this.enquiryId}); @override NewAuditHistoryModalState createState() => NewAuditHistoryModalState(); } // 2. THE STATE CLASS class NewAuditHistoryModalState extends ConsumerState { late ApiService apiService; bool isLoadingHistory = true; List> getAuditHistoryData = []; @override void initState() { super.initState(); apiService = ApiService(); fetchAuditHistory(); } Future fetchAuditHistory() async { try { final response = await apiService.fetchAuditHistory( widget.enquiryId.toString(),'partner_enquiry' ); if (response != null && response['status'] == "success") { if (mounted) { setState(() { getAuditHistoryData = List>.from(response['data']) .where((e) => e['column_name']?.toString() == 'broker_name') .toList(); }); } } } catch (e) { debugPrint("Audit History Error: $e"); } finally { if (mounted) setState(() => isLoadingHistory = false); } } @override Widget build(BuildContext context) { return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.all(16), content: SizedBox( width: MediaQuery.of(context).size.width * 0.55, height: MediaQuery.of(context).size.height * 0.7, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "Audit History", style: GoogleFonts.poppins( fontSize: 14, color: const Color(0xFF50A398), fontWeight: FontWeight.w500, ), ), const Spacer(), IconButton( icon: const Icon(Icons.close, size: 18), onPressed: () => Navigator.pop(context), ), ], ), const Divider(), Expanded( child: isLoadingHistory ? const Center(child: CircularProgressIndicator()) : getAuditHistoryData.isEmpty ? const Center(child: Text("No Audit History Data Found")) : ListView.builder( itemCount: getAuditHistoryData.length, itemBuilder: (context, index) { return _buildTimelineItem( data: getAuditHistoryData[index], isLast: index == getAuditHistoryData.length - 1, ); }, ), ), ], ), ), ); } Widget _buildTimelineItem({ required Map data, required bool isLast, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ const CircleAvatar(radius: 5, backgroundColor: Color(0xFF2E7D6E)), if (!isLast) Container(width: 2, height: 60, color: Colors.grey.shade300), ], ), const SizedBox(width: 12), Expanded( child: Padding( padding: const EdgeInsets.only(bottom: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( DateFormat('dd-MM-yyyy').format(DateTime.parse(data['changed_on'])), style: GoogleFonts.poppins(fontSize: 11), ), const SizedBox(height: 4), Text(data['message'] ?? "-"), // const SizedBox(height: 4), // Text( // "Changed by ${data['changed_by'] ?? '-'}", // style: GoogleFonts.poppins(fontSize: 11), // ), ], ), ), ), ], ); } }