FIX_1_BrokerName HightlightForPolicyVerifyScreen

This commit is contained in:
sanjeev.p 2026-01-31 17:22:28 +05:30
parent 2dd4e281d0
commit 1f34df0d2e
5 changed files with 671 additions and 49 deletions

File diff suppressed because one or more lines are too long

View File

@ -1420,12 +1420,12 @@ class ApiService {
query = 'agent_id=$id';
}
// final url = Uri.parse(
// '${Env.apiUrl}enquiry/enquiryList?$query&only_policy_data=true&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}&staff_id=$selectedStaffId',
// );
final url = Uri.parse(
'http://localhost/nhance_partner_be/enquiry/enquiryList?$query&only_policy_data=true&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}&staff_id=$selectedStaffId',
'${Env.apiUrl}enquiry/enquiryList?$query&only_policy_data=true&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}&staff_id=$selectedStaffId',
);
// final url = Uri.parse(
// 'http://localhost/nhance_partner_be/enquiry/enquiryList?$query&only_policy_data=true&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}&staff_id=$selectedStaffId',
// );
// if (role == 'manager') {
// url = Uri.parse(
@ -2186,15 +2186,18 @@ class ApiService {
}
}
Future<Map<String, dynamic>> fetchAuditHistory(id) async {
Future<Map<String, dynamic>> fetchAuditHistory(id,table) async {
print("fetchAuditHistory - $id ");
if (_token == null) {
await _initializeToken();
}
// final url = Uri.parse('${Env.apiUrl}audit/history?table_name=partner_policy&pk=80',);
// final url = Uri.parse(
// '${Env.apiUrl}audit/history?table_name=partner_policy&pk=${id ?? ''}',
// );
final url = Uri.parse(
'${Env.apiUrl}audit/history?table_name=partner_policy&pk=${id ?? ''}',
'${Env.apiUrl}audit/history?table_name==${table ?? ''}&pk=${id ?? ''}',
);
final headers = {

View File

@ -861,7 +861,7 @@ class policylistState extends ConsumerState<policylist> {
SizedBox(width: 10),
InkWell(
onTap: () {
showAuditHistoryModal(context, item['id']);
showAuditHistoryModal(context, item['policy_id']);
},
child: Container(
padding: EdgeInsets.all(5.4),
@ -1204,7 +1204,7 @@ class AuditHistoryModalState extends ConsumerState<AuditHistoryModal> {
try {
// Use widget.policyId here!
final response = await apiService.fetchAuditHistory(
widget.policyId.toString(),
widget.policyId.toString(),'partner_policy'
);
if (response != null && response['status'] == "success") {

View File

@ -38,14 +38,24 @@ 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;
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);
// --- Controllers (all editable fields)
final List<String> controllerKeys = [
// Policy
@ -343,6 +353,17 @@ class _policyValidationState extends ConsumerState<policyValidation> {
};
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());
}
@ -394,7 +415,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
Future<void> getBroker() async {
print('getBroker called');
setState(() {
isLoading = true;
isLoadingBroker = true;
});
try {
@ -417,7 +438,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingBroker = false;
});
}
}
@ -425,7 +446,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
Future<void> getInsurancPlanType() async {
print('getInsurancPlanType called');
setState(() {
isLoading = true;
isLoadingInsurancePlan = true;
});
try {
@ -451,13 +472,13 @@ class _policyValidationState extends ConsumerState<policyValidation> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingInsurancePlan = false;
});
}
}
Future<void> getPaymentMode() async {
setState(() => isLoading = true);
setState(() => isLoadingPaymentMode = true);
try {
final response = await apiService.fetchMasterDropDown('PaymentMode');
@ -479,22 +500,22 @@ class _policyValidationState extends ConsumerState<policyValidation> {
}
});
} else {
setState(() {
// setState(() {
getPaymentModeData.clear();
filteredPaymentModeData.clear();
});
// });
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() => isLoading = false);
setState(() => isLoadingPaymentMode = false);
}
}
Future<void> getEnquiryVehicleTypeForCommission() async {
setState(() => isLoading = true);
setState(() => isLoadingVehicleTypeForCommission = true);
try {
final response =
@ -528,7 +549,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() => isLoading = false);
setState(() => isLoadingVehicleTypeForCommission = false);
}
}
@ -659,7 +680,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
}
if (!mounted) return;
setState(() => isLoading = true);
setState(() => isLoadingFile = true);
try {
final url = Uri.parse(
@ -675,7 +696,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
print('Exception in getPolicyFilePath: $e\n$st');
} finally {
if (!mounted) return;
setState(() => isLoading = false);
setState(() => isLoadingFile = false);
}
}
@ -888,6 +909,23 @@ class _policyValidationState extends ConsumerState<policyValidation> {
// 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
}
final payload = {"id": policyId, ...dataDetails(), "updated_by": userId};
if (!mounted) return;
@ -935,6 +973,91 @@ class _policyValidationState extends ConsumerState<policyValidation> {
}
}
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;
}
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'];
@ -1136,10 +1259,10 @@ class _policyValidationState extends ConsumerState<policyValidation> {
onChanged: (val) {
if (val != null) {
setState(() {
// setState(() {
// selectedVehicleType = val;
controllers['vehicleType']?.text = val;
});
// });
// setState(() {});
print("Selected Vehicle Type: $val");
}
@ -1252,10 +1375,10 @@ class _policyValidationState extends ConsumerState<policyValidation> {
onChanged: (val) {
if (val != null) {
setState(() {
// setState(() {
// selectedFuelType = val;
controllers['fuelType']?.text = val;
});
// });
// setState(() {});
print("Selected Fuel Type: $val");
}
@ -1267,7 +1390,120 @@ class _policyValidationState extends ConsumerState<policyValidation> {
Widget buildBrokerAsPerEnquiry(BuildContext context) {
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(
@ -1279,16 +1515,48 @@ class _policyValidationState extends ConsumerState<policyValidation> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Broker (as per Enquiry)",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 11,
),
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),
DropdownSearch<Map<String, dynamic>>(
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,
@ -1303,19 +1571,23 @@ class _policyValidationState extends ConsumerState<policyValidation> {
compareFn: (item1, item2) =>
item1['id'].toString() == item2['id'].toString(),
onChanged: (val) {
if (val != null) {
selectedBroker = val['id'].toString();
controllers['enquiry_broker_id_for_commission']?.text =
val['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;
},
),
),
)
],
);
}
@ -1593,6 +1865,8 @@ class _policyValidationState extends ConsumerState<policyValidation> {
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,
@ -1608,6 +1882,20 @@ class _policyValidationState extends ConsumerState<policyValidation> {
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,
@ -1642,7 +1930,20 @@ class _policyValidationState extends ConsumerState<policyValidation> {
return null;
},
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)),
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
@ -2325,7 +2626,7 @@ class _policyValidationState extends ConsumerState<policyValidation> {
SizedBox(height: 10),
Row(
children: [
Expanded(child:buildBrokerAsPerEnquiry(context)),
Expanded(child:buildBrokerAsPerEnquiry(context,widget.item['id'].toString())),
const SizedBox(width: 8),
Expanded(child:buildInsurancPlanType(context)),
],
@ -2333,12 +2634,47 @@ class _policyValidationState extends ConsumerState<policyValidation> {
Row(
children: [
Expanded(
child: _buildInput(
'Broker (as per Document) *',
controllers['broker_name']!,
required: true,
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(
@ -2456,4 +2792,141 @@ class DateInputFormatter extends TextInputFormatter {
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),
// ),
],
),
),
),
],
);
}
}

View File

@ -827,7 +827,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
);
}
Widget buildSelectInsurer(BuildContext context) {
Widget buildSelectInsurer_withoutsearch(BuildContext context) {
// Ensure this list has data BEFORE building dropdown
final List<Map<String, dynamic>> insurerOptions =
@ -956,6 +956,152 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
);
}
Widget buildSelectInsurer(BuildContext context) {
// Ensure this list has data BEFORE building dropdown
final List<Map<String, dynamic>> insurerOptions =
List<Map<String, dynamic>>.from(filteredInsurerData);
// Selected item logic
Map<String, dynamic>? selectedItem;
if (widget.selectedInsurerVal != null &&
widget.selectedInsurerVal.toString().isNotEmpty &&
insurerOptions.isNotEmpty) {
try {
selectedItem = insurerOptions.firstWhere(
(e) => e['id'].toString() == widget.selectedInsurerVal.toString(),
);
} catch (e) {
selectedItem = null;
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 35,
child: Container(
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.14,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: DropdownSearch<Map<String, dynamic>>(
selectedItem: selectedItem,
// FILTER LOGIC: This filters the list as the user types
items: (filter, infiniteScrollProps) {
if (filter.isEmpty) {
return insurerOptions;
}
return insurerOptions
.where((item) => item['short_name']
.toString()
.toLowerCase()
.contains(filter.toLowerCase()))
.toList();
},
itemAsString: (item) => item['short_name']?.toString() ?? '',
compareFn: (item, selected) {
if (selected == null) return false;
return item['id'].toString() == selected['id'].toString();
},
// FIELD UI (The collapsed state)
dropdownBuilder: (context, selectedItem) {
return Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null
? selectedItem['short_name'].toString()
: '',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
},
decoratorProps: DropDownDecoratorProps(
decoration: AppInputDecorations.dropdownDecoration(
label: "Select Insurer",
).copyWith(
filled: true,
fillColor: Colors.white,
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 1,
),
),
),
// POPUP UI (The expanded state with search)
popupProps: PopupProps.menu(
showSearchBox: true, // Enabled searching
fit: FlexFit.loose,
constraints: const BoxConstraints(maxHeight: 300),
menuProps: const MenuProps(backgroundColor: Colors.white),
// Customizing the search field appearance
searchFieldProps: TextFieldProps(
style: GoogleFonts.poppins(fontSize: 13),
decoration: InputDecoration(
hintText: "Search insurer...",
hintStyle: GoogleFonts.poppins(fontSize: 12),
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
decoration: BoxDecoration(
color: isSelected ? Colors.blue.withOpacity(0.1) : Colors.transparent,
),
child: Text(
item['short_name'].toString(),
style: GoogleFonts.inter(
fontSize: 13,
color: Colors.black,
),
),
);
},
),
// ON CHANGE
onChanged: (val) {
if (val == null) return;
// Note: Ensure 'selectedInsurer' is defined in your state/class
selectedInsurer = val['id'];
widget.onInsurerChanged?.call(val['id']);
// Safe UI refresh
Future.microtask(() {
widget.onFilter();
});
},
),
),
),
],
);
}
static const _textStyle = TextStyle(