nhance_partner/lib/presentation/screens/staff/policy/policy_validation.dart
2026-04-03 15:04:34 +05:30

3277 lines
126 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<policyValidation> createState() => _policyValidationState();
}
class _policyValidationState extends ConsumerState<policyValidation> {
// --- 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<bool> brokerMismatchNotifier = ValueNotifier<bool>(false);
final ValueNotifier<bool> isDeletingNotifier = ValueNotifier<bool>(false);
// --- Controllers (all editable fields)
final List<String> 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<String, TextEditingController> controllers;
String? selectedInsuranceId;
String? selectedAgentRentionRate;
String? selectedManagerRentionRate;
String? selectedInsurancePlanTypeId;
List<String> getVehicleTypeData = [];
List<String> filteredVehicleTypeData = [];
List<String> getFuelTypeData = [];
List<String> filteredFuelTypeData = [];
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
List<Map<String, dynamic>> getInsurancPlanTypeData = [];
List<Map<String, dynamic>> filteredInsurancPlanTypeData = [];
List<Map<String, dynamic>> getVehicleTypeForEnquiryData = [];
List<Map<String, dynamic>> filteredVehicleDataForEnquiryData = [];
List<Map<String, dynamic>> getPaymentModeData = [];
List<Map<String, dynamic>> filteredPaymentModeData = [];
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
final _formKey = GlobalKey<FormState>();
String? selectedVehicleTypeForEnquiryID;
String? selectedVehicleTypeForEnquiryVAL;
String? selectedVehicleType;
String? selectedPaymentMode;
String? selectedFuelType;
String? selectedBroker;
String? selectedInsurerName;
String? selectedInsurerShortName;
String? selectedPartnerCodeAndName;
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyFuel =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyBroker =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyVehicleTypeForEnquiry =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyInsurancPlanType =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyPaymentMode =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
bool _hasLoadedInitialData = false;
Map<String, dynamic> 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<String, dynamic> 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<void> _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<String, dynamic>? ?? {};
// Prefill from API / passed data
item.forEach((key, value) {
if (controllers.containsKey(key)) {
controllers[key]!.text = value?.toString() ?? '';
}
});
}
Future<void> 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<Map<String, dynamic>>.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<void> getPaymentMode() async {
setState(() => isLoadingPaymentMode = true);
try {
final response = await apiService.fetchMasterDropDown('PaymentMode');
if (response['status'] == 200 && response['data'] != null) {
final allData = List<Map<String, dynamic>>.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<void> 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<Map<String, dynamic>>.from(response['data']);
print('API Data - $getInsurancPlanTypeData');
filteredInsurancPlanTypeData =
List<Map<String, dynamic>>.from(getInsurancPlanTypeData);
print('originalData - $filteredInsurancPlanTypeData');
});
} else {
getInsurancPlanTypeData = [];
filteredInsurancPlanTypeData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoadingInsurancePlan = false;
});
}
}
Future<void> getEnquiryVehicleTypeForCommission() async {
setState(() => isLoadingVehicleTypeForCommission = true);
try {
final response =
await apiService.fetchMasterDropDown('vehicleType','dropdown');
if (response['status'] == 200 && response['data'] != null) {
final allData = List<Map<String, dynamic>>.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<void> 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<String>.from(response['data']);
// filteredVehicleTypeData = List<String>.from(getVehicleTypeData);
// });
//
// print('Vehicle Types: $getVehicleTypeData');
// } else {
// getVehicleTypeData.clear();
// filteredVehicleTypeData.clear();
// }
// } catch (e) {
// print('Exception occurred: $e');
// } finally {
// setState(() => isLoadingVehicleType = false);
// }
// }
Future<void> getVehicleType() async {
setState(() => isLoadingVehicleType = true);
try {
final response = await apiService.fetchPolicyMasterDropDown('vehicleType');
if (response['status'] == 'success' && response['data'] != null) {
final list = List<String>.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<void> 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<String>.from(response['data']);
// filteredFuelTypeData = List<String>.from(getFuelTypeData);
// });
//
// print('fuelType Types: $getFuelTypeData');
// } else {
// getFuelTypeData.clear();
// filteredFuelTypeData.clear();
// }
// } catch (e) {
// print('Exception occurred: $e');
// } finally {
// setState(() => isLoadingFuelType = false);
// }
// }
Future<void> getFuelType() async {
setState(() => isLoadingFuelType = true);
try {
final response = await apiService.fetchPolicyMasterDropDown('fuelType');
if (response['status'] == 'success' && response['data'] != null) {
final list = List<String>.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<void> 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<void> 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<String, dynamic>;
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<void> _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<void> _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<bool> _confirmBrokerChange(
BuildContext context, {
required String enquiryBroker,
required String documentBroker,
}) async {
return await showDialog<bool>(
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<bool> _confirmChange(BuildContext context) async {
// return await showDialog<bool>(
// 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<bool> _confirmChange(BuildContext context) async {
return await showDialog<bool>(
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<void> _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<void> _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<String>(
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<String>(
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<String, dynamic>? 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<bool>(
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<Map<String, dynamic>>(
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<String, dynamic>? 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<Map<String, dynamic>>(
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<String, dynamic>? 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<Map<String, dynamic>>(
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<String, dynamic>? 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<Map<String, dynamic>>(
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<String, dynamic>? 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<Map<String, dynamic>>(
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<TextInputFormatter>? inputFormatters, // 👈 ADD THIS
ValueChanged<String>? 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<TextEditingValue>(
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<bool>(
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<NewAuditHistoryModal> {
late ApiService apiService;
bool isLoadingHistory = true;
List<Map<String, dynamic>> getAuditHistoryData = [];
@override
void initState() {
super.initState();
apiService = ApiService();
fetchAuditHistory();
}
Future<void> fetchAuditHistory() async {
try {
final response = await apiService.fetchAuditHistory(
widget.enquiryId.toString(),'partner_enquiry'
);
if (response != null && response['status'] == "success") {
if (mounted) {
setState(() {
getAuditHistoryData =
List<Map<String, dynamic>>.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<String, dynamic> 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),
// ),
],
),
),
),
],
);
}
}