3277 lines
126 KiB
Dart
3277 lines
126 KiB
Dart
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),
|
||
// ),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
} |