import 'dart:async'; import 'dart:ui'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:nhance_partner/presentation/screens/dashboard/dashboard_nw.dart'; import 'package:nhance_partner/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart'; import 'package:nhance_partner/presentation/screens/staff/Enquiry/raise_enq_form.dart'; import 'package:nhance_partner/presentation/screens/staff/Enquiry/staff_policy.dart'; import 'package:nhance_partner/presentation/screens/staff/Enquiry/tabs/tab.dart'; import 'package:nhance_partner/presentation/themes/indicators/customizd_multi_file_upload.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../../core/config/env.dart'; import '../../../../core/routing/routes.dart'; import '../../../../core/services/api_service.dart'; import 'package:universal_html/html.dart' as html; import '../../../../data/services/auth_service.dart'; import '../../../../data/utils/Pagination.dart'; import '../../../../data/utils/toastNotification.dart'; import '../../../../data/utils/validators.dart'; import '../../../layouts/main_layout.dart'; import '../../../layouts/responsive_layout.dart'; import '../../../providers/manager_provider.dart'; import '../../../providers/quotation_staff_proivder.dart'; import '../../../providers/userRoleProvider.dart'; import '../../../themes/indicators/customizd_file_upload.dart'; import '../../../themes/indicators/export_btn.dart'; import '../../../themes/indicators/input_field_decoration.dart'; import '../../../themes/indicators/search_field_theme.dart'; import '../../../themes/indicators/side_Drawer_Panel.dart'; import '../../../themes/indicators/text_field_theme.dart'; import '../../../themes/indicators/text_field_theme_inline_editor.dart' hide UpperCaseTextFormatter; import '../../../widgets/custom_Stdate_EnDate_Filter.dart'; import '../assignStaff.dart'; import 'multi_file_list.dart'; class EnquiryListStaffInline extends ConsumerStatefulWidget { const EnquiryListStaffInline({super.key}); @override ConsumerState createState() => EnquiryStaffState(); } class EnquiryStaffState extends ConsumerState { int currentPage = 1; int itemsPerPage = 10; late ApiService apiService; dynamic userId; dynamic idPrimary; dynamic managerId; dynamic dashboardKey; dynamic SelectedStatus; dynamic SelectedStaffId; String? selectedFileNames; PlatformFile? docUploadedFile; dynamic handlerId; String? _token; final _formKey = GlobalKey(); Map fieldErrors = {}; bool _showEditDrawer = false; final raiseEnqKey = GlobalKey(); // List> dataVal = []; List> getStaffData = []; List> originalData = []; List> filteredData = []; bool isLoading = false; dynamic roleId; bool _showFilterRow = false; Map rowExpanded = {}; Map controllers = {}; List tabHeader = [ 'startDate', 'endDate', 'name', 'email', 'mobile', 'code', 'address', 'regNo', 'remarks', ]; Map isEditingRow = {}; Map> rowControllers = {}; //---------------------- Enquiry TAB Initializations Starts -------------------------------- // List> getVehicleTypeData = []; List> filteredVechicleData = []; List> getAgentListData = []; List> filteredAgentData = []; List> getInsurersData = []; List> filteredInsurersData = []; List> getPaymentModeData = []; List> filteredPaymentModeData = []; List> getInsuranceTypeData = []; List> filteredInsuranceData = []; String? selectedVehicleType; String? selectedInsurer; String? selectedInsPlanType; String? selectedAgent; PlatformFile? docUploadedRCFile; PlatformFile? docUploadedIDProof; PlatformFile? docUploadedPrevPolicy; String? selectedPaymentMode; String? selectedRCFile; String? selectedIdProof; String? selectedPrevPolicy; String? selectedRCFileName; String? rcFileUrlFromApi; String? idProofFileUrlFromApi; String? prevPolicyFileUrlFromApi; String? selectedId; Map dataDetails() { final data = { "agent_id": ((roleId == 'handler') || (roleId == 'manager') || (roleId == 'staff')) ? selectedAgent : userId, "name": controllers["name"]?.text, "mobile": controllers["mobile"]?.text, "email": controllers["email"]?.text, "reg_no": controllers["regNo"]?.text, "vehicle_type_id": selectedVehicleType, // "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', // "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', "is_data_created_by_staff": roleId != null ? '1' : '0', // "insurer_id": selectedInsurer, // "rc_file_name": "rc_doc.pdf", // "id_proof_file_name": "id_proof.pdf", // "previous_policy_file_name": "previous_policy.pdf", "remarks": controllers["remarks"]?.text, "assigned_to": selectedStaff, "insurer_id": selectedInsurer, "broker_id": selectedBroker, "manager_id": managerId, "created_by": userId, }; return data; } final GlobalKey>> dropDownKey = GlobalKey>>(); final GlobalKey>> dropDownKeyInsurer = GlobalKey>>(); final GlobalKey>> dropDownKeyAgent = GlobalKey>>(); final GlobalKey>> dropDownSelectPaymentModeKey = GlobalKey>>(); //---------------------- Assign Staff starts -------------------------------- // final GlobalKey>> dropDownKeyInsurerEnqAsgn = GlobalKey>>(); final GlobalKey>> dropDownKeyBroker = GlobalKey>>(); final GlobalKey>> dropDownSelectStaffKey = GlobalKey>>(); List> getStaffDetailsDataEnqAsgn = []; List> filteredStaffDataEnqAsgn = []; String? selectedStaff; String? selectedRegNum; String? selectedStaffName; List> getBrokerData = []; List> filteredBrokerData = []; String? selectedBroker; final ScrollController _horizontalScrollController = ScrollController(); late Timer timer; bool isActionable = false; //---------------------- Assign Staff Ends -------------------------------- // //---------------------- Enquiry TAB Initializations Ends -------------------------------- // @override void initState() { super.initState(); apiService = ApiService(); timer = Timer.periodic(Duration(seconds: 30), (Timer t) => callRefresh()); for (String field in tabHeader) { controllers[field] = TextEditingController(); } _initializeToken(); Future.microtask(() async { // final id = ref.read(managerIdProvider); roleId = ref.read(userRoleProvider); userId = ref.read(userIdProvider); managerId = ref.read(managerIdProvider); final prefs = await SharedPreferences.getInstance(); dashboardKey = prefs.getString('dashboardKeyProvider'); final dashboardStatus = prefs.getString('dashboardStatusProvider'); final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); print('handlerIdENQ - $handlerId'); print("C72 => r : $roleId | uId: $userId !mID : $managerId "); print('dashboardKey - $dashboardKey'); print('dashboardStatus - $dashboardStatus'); print('dashboardStaffId - $dashboardStaffId'); if (managerId != null && dashboardKey != null && dashboardStatus != null && dashboardStaffId != null) { print("DASHBOARD STATus-$dashboardStatus -- $dashboardStaffId -"); SelectedStatus = dashboardStatus; SelectedStaffId = dashboardStaffId; getStaffList( managerId, roleId, SelectedStatus: dashboardStatus, SelectedStaffId: dashboardStaffId, ); } else { print('ELSE'); getStaffList(managerId, roleId); } if (managerId != null) { print('managerId - $managerId'); getAgentList(managerId); } final userID = ref.watch(userIdProvider); print("managerId - $managerId"); if (userID != null) { print('hansles'); getStaffDetailsForEnquiryAssignment(userID); } }); getVehicleType(); getInsurers(); getInsuranceType(); getBroker(); getPaymentMode(); } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } void callRefresh() { print('Call Refresh'); if (!isActionable) { print('Call Refresh isActionable'); refrshfilterDateRange(); } } void refresh() { final id = ref.read(managerIdProvider); final roleId = ref.read(userRoleProvider); final userId = ref.read(userIdProvider); print("C82 => r : $roleId | mId: $id | uId: $userId "); if (managerId != null) { getStaffList(managerId, roleId); } } void filterDateRange() { // ✅ Call your API getStaffList( managerId, roleId, SelectedStatus: SelectedStatus ?? '', SelectedStaffId: SelectedStaffId ?? '', ); } @override void dispose() { _horizontalScrollController.dispose(); // ... dispose other controllers ... super.dispose(); } Future refrshfilterDateRange() async { print('refrshfilterDateRange'); // setState(() async { final prefs = await SharedPreferences.getInstance(); // ✅ Clear any old data await prefs.remove('dashboardKeyProvider'); await prefs.remove('dashboardStatusProvider'); await prefs.remove('dashboardStaffIdProvider'); SelectedStatus = ''; SelectedStaffId = null; controllers['startDate']!.clear(); controllers['endDate']!.clear(); controllers['startDate']?.text = ''; controllers['endDate']?.text = ''; // Reset the FormField validation _formKey.currentState?.reset(); // }); final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0; print('refrshfilterDateRange 1'); getStaffList( // managerId, parsedManagerId, roleId, SelectedStatus: SelectedStatus ?? '', SelectedStaffId: SelectedStaffId ?? '', ); print('refrshfilterDateRange 2'); } Future getInsurers() async { print('Insurers called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Insurers'); if (response['status'] == 200) { print('getInsurers - ${response['data']}'); setState(() { getInsurersData = List>.from(response['data']); print('API Data - $getInsurersData'); filteredInsurersData = List.from(getInsurersData); print('originalData - $filteredInsurersData'); }); } else { getInsurersData = []; filteredInsurersData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getInsuranceType() async { print('getClaimList called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('InsuranceType'); if (response['status'] == 200) { print('getInsuranceTypeData - ${response['data']}'); setState(() { getInsuranceTypeData = List>.from( response['data'], ); print('API Data - $getInsuranceTypeData'); filteredInsuranceData = List.from(getInsuranceTypeData); print('originalData - $filteredInsuranceData'); }); } else { getInsuranceTypeData = []; filteredInsuranceData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getBroker() async { print('getBroker called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('Broker'); if (response['status'] == 200) { print('getBroker - ${response['data']}'); setState(() { getBrokerData = List>.from(response['data']); print('API Data - $getBrokerData'); filteredBrokerData = List.from(getBrokerData); print('originalData - $filteredBrokerData'); }); } else { getBrokerData = []; filteredBrokerData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getPaymentMode() async { print('getPaymentMode called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('PaymentMode'); if (response['status'] == 200) { print('getPaymentModeData - ${response['data']}'); setState(() { getPaymentModeData = List>.from( response['data'], ); print('API Data - $getPaymentModeData'); filteredPaymentModeData = List.from(getPaymentModeData); print('originalData - $filteredPaymentModeData'); }); } else { getPaymentModeData = []; filteredPaymentModeData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getStaffDetailsForEnquiryAssignment(int id) async { print('getStaffDetailsForEnquiryAssignment called By handler'); setState(() { isLoading = true; }); try { // final response = await apiService.fetchStaffUserList(id, role); final response = await apiService.fetchStaffListForEnquiryAssignDropDown( managerId, id, roleId, ); if (response['status'] == 'success') { print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); setState(() { getStaffDetailsDataEnqAsgn = List>.from( response['data'], ); print('API Data - $getStaffDetailsDataEnqAsgn'); filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); print('originalData - $filteredStaffDataEnqAsgn'); }); } else { getStaffDetailsDataEnqAsgn = []; filteredStaffDataEnqAsgn = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getStaffList( int managerId, role, { String fromDate = '', String toDate = '', String SelectedStatus = '', String SelectedStaffId = '', }) async { print('A72 => Fns called => $managerId | $role'); setState(() { isLoading = true; }); dynamic fromDt; dynamic toDt; final fromDateVal = controllers['startDate']?.text ?? ''; final toDateVal = controllers['endDate']?.text ?? ''; final dateFormat = DateFormat('dd-MM-yyyy'); if (dashboardKey != 'fromDashboard' && fromDateVal == '' && toDateVal == '') { print("ENQ LIST PAGE"); final today = DateTime.now(); fromDt = dateFormat.format(today.subtract(Duration(days: 15))); toDt = dateFormat.format(today); print('Enq FROM TO - $fromDt - $toDt'); } else { fromDt = fromDateVal; toDt = toDateVal; } try { final response = await apiService.fetchEnquiryList( managerId, userId, role, fromDate: fromDt, toDate: toDt, selectedStatus: SelectedStatus != null ? SelectedStatus : '', selectedStaffId: SelectedStaffId ?? '', ); if (response['status'] == 'success') { final data = response['data']; final fromDate = response['from_date'] ?? ''; final toDate = response['to_date'] ?? ''; print('FromDate : $fromDate'); print('ToDate : $toDate'); setState(() { controllers['startDate']?.text = fromDate; controllers['endDate']?.text = toDate; if (data is List) { getStaffData = List>.from(data); } else if (data is Map) { getStaffData = [Map.from(data)]; } else { getStaffData = []; } originalData = getStaffData; filteredData = List.from(originalData); }); } else { getStaffData = []; originalData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } List get _paginatedData { // Sort descending by id first final sortedData = [...filteredData] ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); if (sortedData.isEmpty) return []; // Ensure currentPage is valid final maxPage = (sortedData.length / itemsPerPage).ceil(); final safePage = currentPage.clamp(1, maxPage); final startIndex = (safePage - 1) * itemsPerPage; final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length); return sortedData.sublist(startIndex, endIndex); } void filterData(String query) { print("FilterDAta - $query"); setState(() { filteredData = getStaffData.where((item) { // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; return (item['agent_name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['reg_no'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['insurer_name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['assigned_to_name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['insured_name'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['premium_amount'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['payment_mode'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (_formatDate(item['created_on']) ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (_formatDate(item['updated_on']) ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['policy_number'] ?? '-').toLowerCase().contains( query.toLowerCase(), ) || (item['status'] ?? '-').toLowerCase().contains(query.toLowerCase()); }).toList(); }); } final TextEditingController _searchStaffController = TextEditingController(); String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); return DateFormat( 'dd-MM-yyyy HH:mm', ).format(dateTime); // 24-hour format } catch (e) { return rawDate; // fallback if parsing fails } } Future handleStaff( BuildContext context, dynamic data, id, regNum, ) async { showDialog( context: context, builder: (ctx) => AssignStaffDialog( enquiryPrimaryId: id, regNum: regNum, userId: userId, onSubmit: (value) { debugPrint("New assignY: $value"); refresh(); }, ), ); } Future handleProposalAccept(BuildContext context, String id) async { final result = showDialog( context: context, barrierDismissible: true, // allows tapping outside to close builder: (ctx) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(26), ), insetPadding: const EdgeInsets.all(24), child: Container( width: 500, height: 300, color: Colors.white, child: QuotationPopUpTab(id: id, onRefresh: refrshfilterDateRange), ), ); }, ); print("Dialog result: $result"); (); } void handleEdit(Map item) { print("handleEdit"); final id = int.tryParse(item['id'].toString()) ?? 0; idPrimary = item['id']; print("handleEdit - $idPrimary"); setState(() { selectedId = idPrimary; isEditingRow[id] = true; }); for (final field in tabHeader) { // Only set if controller exists if (controllers.containsKey(field)) { controllers[field]!.text = item[field] != null ? item[field].toString() : ''; // fallback to empty string } } //dropdowns or IDs selectedVehicleType = item['vehicle_type_id']; selectedAgent = item['agent_id']; managerId = item['manager_id']; controllers["regNo"]?.text = item['reg_no']; controllers["remarks"]?.text = item['remarks']; selectedInsurer = item['insurer_id']; selectedStaff = item['assigned_to']; setState(() {}); } Future handlePaymentMode(id, quotId) async { Map data = { "quotation_id": quotId, "payment_mode_id": id, "updated_by": userId, }; final path = 'quotation/updateQuoteData'; final res = await apiService.createUserData(data, path); print("handlePaymentMode - $res"); if (res['status'] == 'success') { print("handlePaymentModesuccess"); refrshfilterDateRange(); } } Future handleInsurancePlanTypeMode(id, quotId) async { Map data = { "quotation_id": quotId, "insurance_plan_type_id": id, "updated_by": userId, }; final path = 'quotation/updateQuoteData'; final res = await apiService.createUserData(data, path); print("handleINSMode - $res"); if (res['status'] == 'success') { print("handleINSsuccess"); refrshfilterDateRange(); } } void handleSave(dynamic id) { print('SAVE -- $id'); // final ids = int.tryParse(id.toString()) ?? 0; idPrimary = id; handleEnqTabSave(); } void handleCancel(int id) { print('handleCancel id - $id'); final bool isNewRow = int.tryParse(id.toString()) != null && int.parse(id.toString()) > 1000000000000; print('isNewRow - $isNewRow'); setState(() { if (isNewRow) { // Check if this is a new unsaved row final row = getStaffData.firstWhere( (item) => item['id'].toString() == id.toString(), orElse: () => {}, ); // If policy_number is empty, it's a new row - remove it if (row.isNotEmpty // (row['policy_number'] == null || // row['policy_number'] == '' || // row['policy_number'] == '-') ) { getStaffData.removeWhere( (item) => item['id'].toString() == id.toString(), ); originalData.removeWhere( (item) => item['id'].toString() == id.toString(), ); filteredData.removeWhere( (item) => item['id'].toString() == id.toString(), ); rowControllers.remove(id); } } // Turn off editing mode isEditingRow.remove(id); // // Clear idPrimary if it matches // if (idPrimary.toString() == id.toString()) { // idPrimary = null; // } }); } //-------------------------------Assign Logics staff start ---------------------------// Widget buildSelectStaffMem(ctx) { Map? selectedVehicle; try { selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( (item) => item['id'].toString() == selectedStaff, ); } catch (e) { selectedVehicle = null; // ✅ fallback } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Assigned To', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( height: 30, width: MediaQuery.of(context).size.width * 0.08, child: DropdownSearch>( key: dropDownSelectStaffKey, // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, selectedItem: selectedVehicle, // items: (filter, infiniteScrollProps) { // return filteredStaffDataEnqAsgn; // }, items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredStaffDataEnqAsgn; } return filteredStaffDataEnqAsgn.where((item) { return item['name'].toString().toLowerCase().contains( filter.toLowerCase(), ); }).toList(); }, itemAsString: (val) => val['name'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 15, // smaller icon // icon: const Icon(Icons.arrow_drop_down), ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Staff Member", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['name'].toString() : "", style: GoogleFonts.poppins( fontSize: 11, color: Color(0XFF6366F1), ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, style: GoogleFonts.inter(fontSize: 11, color: Colors.black), decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Search Staff ...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // constraints: BoxConstraints(), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 12, color: Colors.black), ), ); }, ), onChanged: (val) { if (val != null) { print("Selected Staff : ${val['name']}"); print("Id: ${val['id']}"); selectedStaffName = val['name']; selectedStaff = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildInsurer(BuildContext context, {fromHeader = true}) { Map? selectedInsurerd = filteredInsurersData.firstWhere( (item) => item['id'].toString() == selectedInsurer, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Insurer', style: _subLabelTimeStyle), SizedBox(height: 5), Container( height: 30, width: fromHeader ? MediaQuery.of(context).size.width * 0.1 : MediaQuery.of(context).size.width * 0.08, child: DropdownSearch>( key: dropDownKeyInsurerEnqAsgn, selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, // items: (filter, infiniteScrollProps) { // return filteredInsurersData; // }, items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredInsurersData; } return filteredInsurersData.where((item) { return item['name'].toString().toLowerCase().contains( filter.toLowerCase(), ); }).toList(); }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 15, // smaller icon // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['name'].toString() : "", style: GoogleFonts.poppins( fontSize: 11, color: Color(0XFF6366F1), ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Insurer", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 200), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( style: GoogleFonts.inter(fontSize: 11, color: Colors.black), autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Search Insurer...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 12, color: Colors.black), ), ); }, // constraints: BoxConstraints(), ), onChanged: (val) { if (val != null) { print("Selected Insurer : ${val['name']}"); print("Id: ${val['id']}"); selectedInsurer = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildBroker(BuildContext context) { Map? selectedBrokers = filteredBrokerData.firstWhere( (item) => item['id'].toString() == selectedBroker, orElse: () => {}, ); return SizedBox( height: 30, width: MediaQuery.of(context).size.width * 0.07, child: DropdownSearch>( key: dropDownKeyBroker, selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null, items: (filter, infiniteScrollProps) { return filteredBrokerData; }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 15, // smaller icon // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['name'].toString() : "", style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Broker", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Colors.black, width: 0.1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Colors.black, width: 0.1), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( style: GoogleFonts.inter(fontSize: 11, color: Colors.black), decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Search Broker...", hintStyle: GoogleFonts.inter(fontSize: 12, color: Colors.black), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.white), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), child: Text( item['name'].toString(), style: GoogleFonts.inter(fontSize: 12, color: Colors.black), ), ); }, // constraints: BoxConstraints(), ), onChanged: (val) { if (val != null) { print("Selected Broker : ${val['name']}"); print("Id: ${val['id']}"); selectedBroker = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ); } // -------------------------------Assign Logics staff end---------------------------// //---------------------- Enquiry TAB Logins Starts -------------------------------- // Map dataEnquiryTabDetails() { final data = { "agent_id": ((roleId == 'handler') || (roleId == 'manager')) ? selectedAgent : userId, "name": controllers["name"]?.text, "mobile": controllers["mobile"]?.text, "email": controllers["email"]?.text, "reg_no": controllers["regNo"]?.text, "vehicle_type_id": selectedVehicleType, "is_data_created_by_handler": roleId == 'handler' ? '1' : '0', "is_data_created_by_manager": roleId == 'manager' ? '1' : '0', // "insurer_id": selectedInsurer, // "rc_file_name": "rc_doc.pdf", // "id_proof_file_name": "id_proof.pdf", // "previous_policy_file_name": "previous_policy.pdf", "remarks": controllers["remarks"]?.text, "manager_id": managerId, "created_by": userId, }; return data; } Future getVehicleType() async { print('getClaimList called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchMasterDropDown('vehicleType','dropdown'); if (response['status'] == 200) { print('getVehicleTypeData - ${response['data']}'); setState(() { getVehicleTypeData = List>.from( response['data'], ); print('API Data - $getVehicleTypeData'); filteredVechicleData = List.from(getVehicleTypeData); print('originalData - $filteredVechicleData'); }); } else { getVehicleTypeData = []; filteredVechicleData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getAgentList(id) async { print('getAgentListData called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchAgentNameDropDown(id); print('getAgentListData called response'); print('get Agent- ${response['data']}'); if (response['status'] == 'success') { print('get Agent- ${response['data']}'); setState(() { getAgentListData = List>.from(response['data']); print('API Data - $getAgentListData'); filteredAgentData = List.from(getAgentListData); print('originalAgentData - $filteredAgentData'); }); } else { getAgentListData = []; filteredAgentData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Widget buildAgentName(BuildContext context, {fromHeader = true}) { Map? selectedAgntName = filteredAgentData.firstWhere( (item) => item['id'].toString() == selectedAgent, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Partner', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.08, // width: fromHeader // ? MediaQuery.of(context).size.width * 0.11 // : MediaQuery.of(context).size.width * 0.08, height: 30, child: DropdownSearch>( key: dropDownKeyAgent, selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, // items: (filter, infiniteScrollProps) { // return filteredAgentData; // }, items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredAgentData; } return filteredAgentData.where((item) { return item['name'].toString().toLowerCase().contains( filter.toLowerCase(), ); }).toList(); }, itemAsString: (val) => val['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 15, // smaller icon // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['name'].toString() : "", // ✅ FIXED style: GoogleFonts.poppins( fontSize: 11, color: Color(0XFF6366F1), ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Partner Name", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0XFF6366F1), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, style: GoogleFonts.inter(fontSize: 11, color: Colors.black), decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Partner Name", hintStyle: GoogleFonts.inter( fontSize: 10, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // constraints: BoxConstraints(), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( item['name'].toString(), style: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), ), Text( item['agent_code'].toString(), style: GoogleFonts.inter( fontSize: 11, color: Colors.grey, ), ), ], ), ); }, ), onChanged: (val) { if (val != null) { print("Selected Partner : ${val['name']}"); print("Id: ${val['id']}"); selectedAgent = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildVehicleNumber(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Vehicle Number', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.08, child: ThemedFormInlineField( controller: controllers['regNo']!, hintText: 'Vehicle Number', borderColor: Color(0XFF6366F1), textColr: Color(0XFF6366F1), isdense: true, inputFormatters: [ UpperCaseTextFormatter(), // 👈 custom formatter for uppercase FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')), ], validator: (value) => Validators.requiredVechileNum(value, "regNo"), widthNone: true, ), ), ], ); } Widget buildInsuredName(BuildContext context, {fromHeader = true}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Insured', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( width: fromHeader ? MediaQuery.of(context).size.width * 0.1 : MediaQuery.of(context).size.width * 0.08, child: ThemedFormInlineField( controller: controllers['name']!, hintText: 'Insured Name', textColr: Color(0XFF6366F1), // errorText: fieldErrors['name'], borderColor: Color(0XFF6366F1), isdense: true, validator: (value) => Validators.requiredField(value, "name"), widthNone: true, ), ), ], ); } Widget buildVehicleType(BuildContext context) { // Find the matching map from your list Map? selectedVehicle = filteredVechicleData.firstWhere( (item) => item['id'].toString() == selectedVehicleType, orElse: () => {}, ); return SizedBox( width: MediaQuery.of(context).size.width * 0.07, height: 30, child: DropdownSearch>( key: dropDownKey, selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, items: (filter, infiniteScrollProps) { return filteredVechicleData; }, validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, itemAsString: (val) => val['vehicle_type'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 15, // smaller icon // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['vehicle_type'].toString() : "", // ✅ FIXED style: GoogleFonts.poppins(fontSize: 11, color: Color(0XFF6366F1)), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Vehicle Type", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Colors.black, width: 0.1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Colors.black, width: 0.1), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 200), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( style: GoogleFonts.inter(fontSize: 11, color: Colors.black), padding: const EdgeInsets.all(1.0), decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Search Vehicle Type...", hintStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), labelStyle: GoogleFonts.inter(fontSize: 10, color: Colors.black), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.white), // 👈 Normal border ), isDense: true, // reduces height // contentPadding: const EdgeInsets.symmetric( // horizontal: 8, // vertical: 8, // reduce this value for smaller height // ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), // constraints: BoxConstraints(), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), child: Text( item['vehicle_type'].toString(), style: GoogleFonts.inter(fontSize: 12, color: Colors.black), ), ); }, ), onChanged: (val) { if (val != null) { print("Selected vehicle_type : ${val['vehicle_type']}"); print("Id: ${val['id']}"); selectedVehicleType = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ); } Widget buildEmail(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Email', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.1, child: ThemedFormInlineField( controller: controllers['email']!, borderColor: Color(0XFF6366F1), textColr: Color(0XFF6366F1), hintText: 'Email', isdense: true, validator: (value) => Validators.nonReqemail(value, "email"), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')), ], txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.16, ), ), ], ); } Widget buildPhNumber(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Mobile', style: _subLabelTimeStyle), SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.1, child: ThemedFormInlineField( controller: controllers['mobile']!, borderColor: Color(0XFF6366F1), textColr: Color(0XFF6366F1), hintText: 'Mobile', isdense: true, validator: (value) => Validators.nonReqphone(value, "phone"), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]')), ], txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.26, ), ), ], ); } Widget buildRemarks(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Remarks', style: _subLabelTimeStyle), SizedBox(height: 5), ThemedFormField( // maxLength: 500, enableBorderWidth: 0.5, hintText: 'Remarks', controller: controllers['remarks']!, borderColor: Color(0XFF6366F1), textColr: Color(0XFF6366F1), verticalPad: 10, horizonalPad: 10, isdense: true, // keyboardType: TextInputType.multiline, txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.1, ), ], ); } Widget buildPaymentMode(context) { Map? selectedVehicle = filteredPaymentModeData.firstWhere( (item) => item['id'].toString() == selectedPaymentMode, orElse: () => {}, ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Payment Mode", style: _textStyle), // SizedBox(height: 10), SizedBox( // color: Colors.red, // color: Colors.yellow, width: 200, // height: 30, // width: ResponsiveLayout.isMobile(context) // ? null // : MediaQuery.of(context).size.width * 0.18, // height: 35, child: DropdownSearch>( key: dropDownSelectPaymentModeKey, selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, // items: (filter, infiniteScrollProps) { // return filteredInsuranceData; // }, items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredPaymentModeData; } return filteredPaymentModeData.where((item) { return item['name'].toString().toLowerCase().contains( filter.toLowerCase(), ); }).toList(); }, itemAsString: (val) => val['value'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id validator: (val) { if (val == null) { return "Required"; // ✅ error message } return null; }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Payment Mode", ).copyWith( filled: true, fillColor: Colors.white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.dialog( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 400, maxWidth: 300), // constraints: BoxConstraints(maxHeight: 250), dialogProps: DialogProps(backgroundColor: Colors.white), // menuProps: MenuProps( // backgroundColor: // Colors.white, // 👈 sets dropdown background to white // ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.all(1), filled: true, fillColor: Colors.white, hintText: "Search Payment Mode ...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), // 👈 Normal border ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, width: 1.5, ), // 👈 Focused border ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['value'].toString(), style: GoogleFonts.inter(fontSize: 12, color: Colors.black), ), ); }, ), onChanged: (val) { if (val != null) { print("Selected value : ${val['value']}"); print("Id: ${val['id']}"); selectedPaymentMode = val['id']; // controllers['agentId']?.text = val['agent_code']; // agentId = agent['id']; } }, ), ), ], ); } Widget buildResponsiveUploadField({ required String label, required String? hintText, required double? width, required void Function(String fileName, dynamic file) onFileSelected, bool showDownload = false, VoidCallback? onRemove, VoidCallback? onDownload, }) { final isMobile = ResponsiveLayout.isMobile(context); final uploadWidget = ThemedMultiFileUploadField( hintText: hintText ?? "Upload Document", txtwidth: width, // txtwidth: MediaQuery.of(context).size.width * 0.08, padVertical: 6, padHorizontal: 8, backgroundColor: Colors.white, // backgroundColor: Color(0xffD9EBE8), txtName: label, isTxtBtnCase: true, onFileSelected: onFileSelected, ); // final uploadWidget = ThemedUploadField( // hintText: hintText ?? "Upload Document", // txtwidth: width, // // txtwidth: MediaQuery.of(context).size.width * 0.08, // padVertical: 6, // padHorizontal: 8, // // backgroundColor: Colors.white, // // backgroundColor: Color(0xffD9EBE8), // txtName: label, // isTxtBtnCase: true, // onFileSelected: onFileSelected, // ); return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [uploadWidget], ), ], ); } Widget buildUploadRCDocument(BuildContext context) { return SizedBox( // width: MediaQuery.of(context).size.width * 0.08, child: buildResponsiveUploadField( label: "RC", width: 60, hintText: selectedRCFile, onFileSelected: (fileName, file) { setState(() { docUploadedRCFile = file; selectedRCFileName = fileName; }); ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); }, showDownload: docUploadedRCFile != null || rcFileUrlFromApi != null, onRemove: () { setState(() { docUploadedRCFile = null; rcFileUrlFromApi = null; selectedRCFileName = null; }); }, onDownload: () => apiService.downloadFile( // apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId', apiUrl: 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc', apiId: selectedId.toString(), localFile: docUploadedRCFile, fileName: 'RC', ), ), ); } Widget buildUploadIDDocument(BuildContext context) { return SizedBox( // width: MediaQuery.of(context).size.width * 0.08, child: buildResponsiveUploadField( label: "ID Proof", width: 80, hintText: selectedIdProof, onFileSelected: (fileName, file) { setState(() { docUploadedIDProof = file; // selectedFileNames = fileName; }); ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); }, showDownload: docUploadedIDProof != null || idProofFileUrlFromApi != null, onRemove: () { setState(() { docUploadedIDProof = null; idProofFileUrlFromApi = null; // selectedFileNames = null; }); }, onDownload: () => apiService.downloadFile( // apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId', apiUrl: 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof', apiId: selectedId.toString(), localFile: docUploadedIDProof, fileName: 'Id_Proof', ), ), ); } Widget buildUploadPolicyDocument(BuildContext context) { return SizedBox( // width: MediaQuery.of(context).size.width * 0.08, child: buildResponsiveUploadField( width: 100, label: "PrevPolicy", hintText: selectedPrevPolicy, onFileSelected: (fileName, file) { setState(() { docUploadedPrevPolicy = file; // selectedFileNames = fileName; }); ToastHelper.showSuccessToast(context, 'File Uploaded Successfully'); }, showDownload: docUploadedPrevPolicy != null || prevPolicyFileUrlFromApi != null, onRemove: () { setState(() { docUploadedPrevPolicy = null; prevPolicyFileUrlFromApi = null; // selectedFileNames = null; }); }, onDownload: () => apiService.downloadFile( // apiUrl: 'agent/downloadAgentCertificateFile?agent_id=$selectedId', apiUrl: 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy', apiId: selectedId.toString(), localFile: docUploadedPrevPolicy, fileName: 'Previous_Policy', ), ), ); } Widget buildDocRC(BuildContext context, selectedEnquiryId) { return SizedBox( width: MediaQuery.of(context).size.width * 0.16, child: InkWell( onTap: () { print("Upload $selectedEnquiryId"); final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; print("Uploadpath $path"); apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Tooltip( message: 'Download RC', child: Text( "RC", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ), ], ), ), ), ); } Widget buildDocIdProof(BuildContext context, selectedEnquiryId) { return InkWell( onTap: () { print("Upload $selectedEnquiryId"); final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof'; print("Uploadpath $path"); apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Tooltip( message: 'Download ID Proof', child: Text( "ID Proof", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ), ], ), ), ); } Widget buildDocPrevPolicy(BuildContext context, selectedEnquiryId) { return InkWell( onTap: () { print("QD597 Upload $selectedEnquiryId"); final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy'; print("Uploadpath $path"); apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Tooltip( message: 'Download Previous Policy', child: Text( "Prev Policy", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ), ], ), ), ); } // Update validation method to set errors bool _validateRequiredFields() { setState(() { fieldErrors.clear(); // Clear previous errors // Validate Partner/Agent if (selectedAgent == null || selectedAgent == '') { fieldErrors['agent'] = 'Partner is required'; } // Validate Insured Name if (controllers['name']?.text.trim().isEmpty ?? true) { fieldErrors['name'] = 'Insured Name is required'; } // Validate Vehicle Number if (controllers['regNo']?.text.trim().isEmpty ?? true) { fieldErrors['regNo'] = 'Vehicle Number is required'; } // Validate Vehicle Type if (selectedVehicleType == null || selectedVehicleType == '') { fieldErrors['vehicleType'] = 'Vehicle Type is required'; } // Validate email format if provided final email = controllers['email']?.text.trim() ?? ''; if (email.isNotEmpty) { final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(email)) { fieldErrors['email'] = 'Invalid email format'; } } // Validate mobile number if provided final mobile = controllers['mobile']?.text.trim() ?? ''; if (mobile.isNotEmpty) { final cleanMobile = mobile.replaceAll(' ', ''); if (cleanMobile.length < 10) { fieldErrors['mobile'] = 'Must be at least 10 digits'; } } }); return true; return fieldErrors.isEmpty; } Future handleEnqTabSave() async { if (!_validateRequiredFields()) return; // setState(() { // isSaving = true; // start saving // }); final dataSet = dataDetails(); try { await createUserData(dataSet); // API call // ToastHelper.showInfoToast(context, "Enquiry saved successfully"); } catch (e) { showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Error'), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('OK'), ), ], ), ); } finally { // setState(() => isSaving = false); // re-enable button after save } } Future attachFiles(http.MultipartRequest request) async { Future addFileOrKeepName( PlatformFile? file, String? apiFileName, String fieldName, ) async { if (file != null) { // User uploaded a new file → send as multipart if (file.bytes != null) { request.files.add( http.MultipartFile.fromBytes( fieldName, file.bytes!, filename: file.name, ), ); } else if (file.path != null) { request.files.add( await http.MultipartFile.fromPath( fieldName, file.path!, filename: file.name, ), ); } print("📎 Attached new file → $fieldName"); } else if (apiFileName != null && apiFileName.isNotEmpty) { // No new upload → tell backend to keep old file request.fields[fieldName] = apiFileName; print("🔗 Kept old file → $fieldName = $apiFileName"); } else { // Nothing at all request.fields[fieldName] = ""; } } await addFileOrKeepName( docUploadedRCFile, rcFileUrlFromApi, 'rc_file_name', ); await addFileOrKeepName( docUploadedIDProof, idProofFileUrlFromApi, 'id_proof_file_name', ); await addFileOrKeepName( docUploadedPrevPolicy, prevPolicyFileUrlFromApi, 'previous_policy_file_name', ); } List _buildPopupDownload(BuildContext context, selectId) { bool isMobile = ResponsiveLayout.isMobile(context); return [ // editing // ? Container( // color: Colors.red.shade50, padding: EdgeInsets.only(top: 10), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ buildUploadRCDocument(context), SizedBox(width: 3), buildUploadIDDocument(context), SizedBox(width: 3), buildUploadPolicyDocument(context), ], ), ), ]; } Widget _buildSubRow(context, selectId, id) { return Wrap( spacing: 12, runSpacing: 12, children: [ buildSelectStaffMem(context), buildAgentName(context, fromHeader: false), buildInsurer(context), buildInsuredName(context), buildVehicleNumber(context), buildEmail(context), buildPhNumber(context), buildRemarks(context), // Padding( // padding: const EdgeInsets.only(top: 8.0), // child: Builder( // builder: (buttonContext) => Material( // // color: Color(0xFFFFFCF2), // color: Color(0xFFF8F9FF), // child: InkWell( // hoverColor: Color(0xFFF8F9FF), // // hoverColor: const Color(0xFFFFFCF2), // // hoverColor: const Color(0xFFEAF6F4), // onTap: () async { // // ✅ Get overlay and button position RELATIVE to InkWell // final RenderBox button = // buttonContext.findRenderObject() as RenderBox; // final RenderBox overlay = // Overlay.of(buttonContext).context.findRenderObject() // as RenderBox; // // final Offset position = button.localToGlobal( // Offset.zero, // ancestor: overlay, // ); // // await showMenu( // context: buttonContext, // position: RelativeRect.fromLTRB( // position.dx, // position.dy + button.size.height, // overlay.size.width, // 0, // ), // items: [ // PopupMenuItem( // child: Column( // mainAxisSize: MainAxisSize.min, // children: _buildPopupDownload( // buttonContext, // selectId, // ), // ), // ), // ], // // color: Colors.white, // // color: Color(0xFFFFFCF2), // color: Color(0xFFF8F9FF), // ); // }, // child: Tooltip( // message: 'Click To Upload Documents', // // waitDuration: const Duration(milliseconds: 500), // showDuration: const Duration(seconds: 2), // child: Container( // padding: EdgeInsets.all(8.0), // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(50), // ), // child: Icon( // Icons.upload_file, // color: Color(0XFF6366F1), // // editing ? Icons.upload_file : Icons.download_for_offline, // // color: editing ? Colors.b lue : Colors.green, // size: 20, // ), // ), // ), // ), // ), // ), // ), Padding( padding: const EdgeInsets.only(top: 8.0), child: IconButton( tooltip: 'Save', padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 24, minHeight: 24), icon: const Icon( Icons.check, color: Colors.green, size: 28, // smaller icon ), splashRadius: 14, // ripple radius onPressed: () => { setState(() { rowExpanded[id] = !(rowExpanded[id] ?? false); }), handleSave(selectId), }, ), ), ], ); } List _buildPopupDownload_BTN( BuildContext context, dynamic data, id, regNum, ) { bool isMobile = ResponsiveLayout.isMobile(context); return [ Row( children: [ buildDocRC_BTN(context, isMobile, id), Spacer(), // SizedBox(width: 3), buildDocIdProof_BTN(context, isMobile, id), Spacer(), buildDocPrevPolicy_BTN(context, isMobile, id), ], ), ]; } Widget buildDocRC_BTN( BuildContext context, bool isMobile, selectedEnquiryId, ) { return GestureDetector( onTap: () { final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=rc'; apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "RC", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ?isMobile ? null : SizedBox(width: 2), // isMobile ? const Spacer() : const SizedBox(width: 15), const Icon( Icons.file_download_outlined, size: 16, color: Colors.white, ), ], ), ), ); } Widget buildDocIdProof_BTN( BuildContext context, bool isMobile, selectedEnquiryId, ) { return GestureDetector( onTap: () { print("Upload $selectedEnquiryId"); final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=id_proof'; apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "ID Proof", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ?isMobile ? null : SizedBox(width: 2), // const SizedBox(width: 15), Icon(Icons.file_download_outlined, size: 16, color: Colors.white), ], ), ), ); } Widget buildDocPrevPolicy_BTN( BuildContext context, bool isMobile, selectedEnquiryId, ) { return GestureDetector( onTap: () { print("QD597 Upload $selectedEnquiryId"); final selectedId = selectedEnquiryId; // final path = // 'agent/downloadAgentIncentiveFile?id=$selectedId'; final path = 'enquiry/downloadEnquiryFile?enquiry_id=$selectedId&file_type=previous_policy'; apiService.getPdfDownload(path, selectedId); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: const Color(0xFF425B5B), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Previous Policy", style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white, ), ), ?isMobile ? null : SizedBox(width: 2), // isMobile ? const Spacer() : const SizedBox(width: 15), Icon(Icons.file_download_outlined, size: 16, color: Colors.white), ], ), ), ); } Future createUserData(Map userData) async { final bool isNewRow = idPrimary != null && int.tryParse(idPrimary.toString()) != null && int.parse(idPrimary.toString()) > 1000000000000; final bool isUpdating = idPrimary != null && idPrimary != 'null' && !isNewRow; print('isNewRow: $isNewRow, isUpdating: $isUpdating'); // final bool isUpdating = idPrimary != 'null'; final id = idPrimary; print('UPDId- $id'); // final bool isUpdating = false; final uri = Uri.parse( isUpdating ? '${Env.apiUrl}enquiry/updateEnquiry' : '${Env.apiUrl}enquiry/createEnquiry', ); if (_token == null) { throw Exception('Token not found. Please log in.'); } // Use MultipartRequest (POST only) final request = http.MultipartRequest('POST', uri); request.headers['Authorization'] = 'Bearer $_token'; request.headers['app-signature'] = Env.App_Signature; // If updating, spoof the method Laravel-style if (isUpdating) { request.fields['id'] = id!; request.fields['updated_by'] = userId!.toString(); } else { request.fields['created_by'] = userId!.toString(); } print("USerDAta - $userData"); // userData.forEach((key, value) { // request.fields[key] = value.toString(); // print("✅ Encoded travel_details2: ${request.fields[key]}"); // }); await attachFiles(request); userData.forEach((key, value) { if (key != 'rc_file_name' && key != 'id_proof_file_name') { request.fields[key] = value.toString(); print("✅ Encoded $key: ${request.fields[key]}"); } else { print('Something Missing..'); } }); // attach files // request.fields['agent_id'] = selectedId.toString(); print(" Sending request with fields: ${request.fields}"); try { final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); print("Response status: ${response.statusCode}"); print("Response body: ${response.body}"); if (response.statusCode == 200 || response.statusCode == 201) { // dispose(); print("✅ Enquiry submitted successfully!"); ToastHelper.showSuccessToast(context, 'Saved Enquiry'); print("Response: ${response.body}"); // Clear the temporary ID idPrimary = null; refrshfilterDateRange(); // getStaffList(managerId, roleId); // setState(() => isSaving = false); } else if (response.statusCode == 403) { await apiService.clearLocalStorageAndRedirect(); } else { print("❌ Submission failed. Status: ${response.statusCode}"); print("Body: ${response.body}"); // setState(() => isSaving = false); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("Enquiry Creation Failed"), content: Text( "There was a problem in creating enquiry. Please try again.", ), actions: [ TextButton( child: Text("OK"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } } catch (e) { print("🔥 Error submitting user: $e"); } } Future uploadPolicyPDF({ required Map data, required PlatformFile? file, }) async { if (_token == null) throw Exception("Token missing. Please login."); final uri = Uri.parse('${Env.apiUrl}policy/uploadPolicyFile'); final request = http.MultipartRequest('POST', uri); request.headers['Authorization'] = 'Bearer $_token'; request.headers['app-signature'] = Env.App_Signature; print("🔥 Upload Request Data: $data"); // 1️⃣ Add text fields (except file) data.forEach((key, value) { if (value != null && key != 'policy_pdf_file_name') { request.fields[key] = value.toString(); print("➡️ Field Added: $key = ${request.fields[key]}"); } }); // 2️⃣ Attach file if (file != null) { if (file.bytes != null) { request.files.add( http.MultipartFile.fromBytes( 'policy_pdf_file_name', file.bytes!, filename: file.name, ), ); } else if (file.path != null) { request.files.add( await http.MultipartFile.fromPath( 'policy_pdf_file_name', file.path!, filename: file.name, ), ); } print("📎 File attached: ${file.name}"); } else { print("⚠️ No file selected!"); } // 3️⃣ Submit request try { final response = await http.Response.fromStream(await request.send()); print("📥 Response Code: ${response.statusCode}"); print("📥 Response Body: ${response.body}"); if (response.statusCode == 200 || response.statusCode == 201) { ToastHelper.showSuccessToast(context, "Policy PDF Uploaded!"); refrshfilterDateRange(); return; } if (response.statusCode == 403) { await apiService.clearLocalStorageAndRedirect(); return; } ToastHelper.showErrorToast(context, "Upload failed!"); } catch (e) { print("❌ Error uploading file: $e"); } } //---------------------- Enquiry TAB Logins End -------------------------------- // @override Widget build(BuildContext context) { return MainLayout( title: "Enquiries", body: Container( // color: Colors.yellow.shade50, // color: Color(0xFFFCFCFC), // color: Colors.white, // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(12), // boxShadow: [ // BoxShadow( // color: Colors.black.withOpacity(0.08), // blurRadius: 8, // spreadRadius: 1, // offset: const Offset(0, 2), // soft drop shadow // ), // ], // ), width: MediaQuery.of(context).size.width, // height: MediaQuery.of(context).size.height * 0.1, // padding: EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Container( // height: MediaQuery.of(context).size.height*0. child: RaiseEnqForm( key: raiseEnqKey, onSubmit: (dataSet) async { print('Check checking dataSet $dataSet'); try { await createUserData(dataSet); // <-- API call here print("Enquiry saved successfully"); // RESET FORM raiseEnqKey.currentState?.resetFormFields(); } catch (e) { showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Error'), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('OK'), ), ], ), ); } }, ), ), // SizedBox(height: 5), ResponsiveLayout.isMobile(context) ? Container( height: MediaQuery.of(context).size.height * 0.69, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(2), child: _buildContent(context), ), ), ) : // Expanded( // child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height * 0.69, // padding: EdgeInsets.symmetric( // horizontal: 8.0, // vertical: 2.0, // ), child: _buildContent(context), ), // ), // Container( // // height: 20, // width: MediaQuery.of(context).size.width, // // color: Colors.green.shade50, // child: PaginationControls( // currentPage: currentPage, // itemsPerPage: itemsPerPage, // // totalItems: dataVal.length, // totalItems: filteredData.length, // // activeColor: layoutColor, // your theme color // onPageChanged: (page) { // setState(() { // currentPage = page; // }); // }, // onItemsPerPageChanged: (items) { // setState(() { // itemsPerPage = items; // currentPage = 1; // }); // }, // ), // ), ], ), ), ); } Future buildStatusActions(BuildContext context, status, id) async { print("Actionsstatus - $status -$id"); dynamic val; if (status == 'Awaiting Proposal' || status == 'Proposal Created') { val = ''; } else { val = 'Policy'; } final result = await showDialog( context: context, barrierDismissible: false, // optional - prevents closing by tapping outside builder: (context) => TabEnquiryStaffList(showKey: val), ); // ✅ Code here runs *after* the dialog is closed print("Dialog closed"); print("Dialog result: $result"); refrshfilterDateRange(); } Future buildPolicyCreatedStatusActions( BuildContext context, status, id, ) async { print("Actionsstatus - $status -$id"); dynamic val; if (status == 'Awaiting Proposal' || status == 'Proposal Created') { val = ''; } else { val = 'Policy'; } final result = await showDialog( context: context, barrierDismissible: false, // optional - prevents closing by tapping outside builder: (context) => PolicyStaffEnqList(), ); // ✅ Code here runs *after* the dialog is closed print("Dialog closed"); print("Dialog result: $result"); if (result) { setState(() { isActionable = false; }); } refrshfilterDateRange(); } Widget _buildContent(BuildContext context) { final isDesktop = !ResponsiveLayout.isMobile(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10), Container( // height: 40, // color: Color(0xffF1F5F9), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ if (_showFilterRow) ...[ DateFilterRow( key: ValueKey(SelectedStatus ?? ''), role: roleId, id: userId, selectedStatusVal: SelectedStatus, onFilterStaff: (val) { print('Selected Filterd STAFF Id - $val'); SelectedStaffId = val; }, selectedStaffId: SelectedStaffId, startController: controllers['startDate']!, endController: controllers['endDate']!, onStatusChanged: (val) { SelectedStatus = val; // update parent }, formKey: _formKey, isMobile: ResponsiveLayout.isMobile(context), onFilter: () { // call your filter logic filterDateRange(); }, onRefresh: () { // call your refresh logic refrshfilterDateRange(); }, ), ], IconButton( icon: Icon( _showFilterRow ? Icons.close_fullscreen : Icons.filter_alt_outlined, ), iconSize: 18, color: const Color(0xFF94A3B8), tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters', onPressed: () { setState(() { _showFilterRow = !_showFilterRow; }); }, ), ThemedSearchField( hintText: 'Search', backgroundColor: Color(0xFFFFFFFF), txtHeight: 30, // backgroundColor: Colors.white, onChanged: filterData, controller: _searchStaffController, txtwidth: ResponsiveLayout.isMobile(context) ? MediaQuery.of(context).size.width * 0.7 : MediaQuery.of(context).size.width * 0.13, ), SizedBox(width: 10), ExportBtn( sheetName: "Enquiry", fileName: "Enquiry_list", data: filteredData, txt: !ResponsiveLayout.isMobile(context) ? true : false, displayHeaders: [ "Received Date & Time", "Partner", "Assigned To", "Insurer", "Vehicle.No.", "Insured Name", "Assigned Date & Time", "Premium", "Payment Mode", "Policy Number", "Status", ], keys: [ "created_on", "agent_name", "assigned_to_name", "insurer_name", "reg_no", "insured_name", "updated_on", "premium_amount", "payment_mode", "policy_number", "status", ], ), ], ), ), SizedBox(height: 5), ResponsiveLayout.isMobile(context) ? _buildDataTable(context) : // Expanded( // child: Container( height: MediaQuery.of(context).size.height * 0.55, decoration: BoxDecoration(color: Colors.white), child: _buildDataTable(context), ), // ), ], ); } // 1. Add group expansion state tracking Map groupExpanded = { // 'Awaiting Proposal': true, // 'Proposal Created': true, // 'Proposal Accepted': true, // 'Proposal Rejected': true, 'Awaiting Payment': true, 'Policy Created': true, }; Map statusGroupMap = { // 'Awaiting Proposal': 'Awaiting Proposal', // 'Proposal Created': 'Proposal Created', // 'Proposal Accepted': 'Proposal Created', // 'Proposal Rejected': 'Proposal Created', 'Proposal Created': 'Awaiting Payment', 'Proposal Accepted': 'Awaiting Payment', 'Proposal Rejected': 'Awaiting Payment', 'Policy Created': 'Policy Created', }; // Add group enquiries by status Map>> get _groupedEnquiries { final grouped = >>{}; // Initialize all status groups // grouped['Awaiting Proposal'] = []; // grouped['Proposal Created'] = []; // grouped['Proposal Accepted'] = []; // grouped['Proposal Rejected'] = []; // grouped['Policy Created'] = []; grouped['Awaiting Payment'] = []; // Initialize only the groups you need // grouped['Awaiting Proposal'] = []; // grouped['Proposal Created'] = []; grouped['Policy Created'] = []; // Group the filtered/paginated data by status // for (var item in _paginatedData) { // final status = item['status']?.toString() ?? 'Unknown'; // if (grouped.containsKey(status)) { // grouped[status]!.add(item); // } // } for (var item in filteredData) { final status = item['status']?.toString() ?? 'Unknown'; // Map actual status to grouped status final groupKey = statusGroupMap[status]; if (groupKey != null) { grouped[groupKey]!.add(item); } } // for (var item in _paginatedData) { // final status = item['status']?.toString() ?? 'Unknown'; // // // Map actual status to grouped status // final groupKey = statusGroupMap[status]; // // if (groupKey != null) { // grouped[groupKey]!.add(item); // } // } return grouped; } Map _getStatusConfig(String status) { switch (status) { case 'Awaiting Proposal': return {'icon': '🕐', 'color': Color(0xFFfff3cd)}; case 'Proposal Created': return {'icon': '📝', 'color': Color(0xFFd1ecf1)}; // case 'Proposal Accepted': // return {'icon': '✅', 'color': Color(0xFFd4edda)}; // case 'Proposal Rejected': // return {'icon': '❌', 'color': Color(0xFFf8d7da)}; case 'Policy Created': return {'icon': '📋', 'color': Color(0xFFcfe2ff)}; default: return {'icon': '📄', 'color': Color(0xFFe9ecef)}; } } final Map statusColors = { 'Awaiting Proposal': const Color(0xFFEFF6FF), // Blue 50 'Proposal Created': const Color(0xFFF0F9FF), // Cyan 50 'Proposal Accepted': const Color(0xFFF0FDF4), // Green 50 'Proposal Rejected': const Color(0xFFFEF9C3), // Yellow 50 'Policy Created': const Color(0xFFF3F0FF), // Purple 50 (generated) }; final Map statusBorderColors = { 'Awaiting Proposal': const Color(0xFFDBEAFE), // Blue 100 'Proposal Created': const Color(0xFFBAE6FD), // Cyan 100 'Proposal Accepted': const Color(0xFFDCFCE7), // Green 100 'Proposal Rejected': const Color(0xFFFEF3C7), // Yellow 100 'Policy Created': const Color(0xFFF0E4FF), // Purple 200 // 'Policy Created': const Color(0xFFE5CCFF), // Purple 200 }; final Map statusTextColors = { 'Awaiting Proposal': const Color(0xFF1D4ED8), // Blue 700 'Proposal Created': const Color(0xFF0369A1), // Cyan 700 'Proposal Accepted': const Color(0xFF15803D), // Green 700 'Proposal Rejected': const Color(0xFFB45309), // Amber 700 'Policy Created': const Color(0xFF6B21A8), // Purple 700 }; // 4. Build group header widget Widget _buildGroupHeader(String status, int count) { final config = _getStatusConfig(status); final isExpanded = groupExpanded[status] ?? true; return InkWell( onTap: () { setState(() { groupExpanded[status] = !isExpanded; }); }, child: Container( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: Row( children: [ Icon( isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, size: 22, color: Color(0xFF5A6C7D), ), SizedBox(width: 8), Text( // '${config['icon']} $status', '$status', style: GoogleFonts.inter( fontSize: 13, color: Colors.black, // color: Colors.orange, fontWeight: FontWeight.w600, ), ), // Spacer(), SizedBox(width: 8), Container( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), decoration: BoxDecoration( color: Color(0xFFEFF6FF), borderRadius: BorderRadius.circular(12), ), child: Text( '$count', style: GoogleFonts.inter( fontSize: 11, color: Color(0xFF2563EB), fontWeight: FontWeight.w500, ), ), ), ], ), ), ); } Widget _buildDataTable(context) { final screenWidth = MediaQuery.of(context).size.width; final isMobile = ResponsiveLayout.isMobile(context); // Calculate minimum width based on screen size double minTableWidth = isMobile ? screenWidth * 1.5 : 1300; return // Expanded( // child: LayoutBuilder( builder: (context, constraints) { // double minWidth = constraints.maxWidth < 1300 // ? 1300 // : constraints.maxWidth; double tableWidth = constraints.maxWidth > minTableWidth ? constraints.maxWidth : minTableWidth; final isDesktop = !ResponsiveLayout.isMobile(context); return ScrollConfiguration( behavior: const MaterialScrollBehavior().copyWith( dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, ), child: SingleChildScrollView( controller: _horizontalScrollController, // 👈 Shared controller scrollDirection: Axis.horizontal, child: SizedBox( // width: minWidth, width: tableWidth, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Fixed Header Container( decoration: BoxDecoration( // color: Colors.white, color: Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(5), // boxShadow: [ // BoxShadow( // color: Colors.black.withOpacity(0.08), // blurRadius: 8, // spreadRadius: 1, // offset: const Offset(0, 2), // ), // ], ), child: _buildTableHeader(isDesktop), ), // Scrollable Data Content Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, // border: Border.all(color: Colors.grey, width: 0.1), // boxShadow: [ // BoxShadow( // color: Colors.black.withOpacity(0.08), // blurRadius: 8, // spreadRadius: 1, // offset: const Offset(0, 2), // ), // ], ), child: _buildDataTableContent(), // 👈 New method ), ), ], ), ), ), ); }, ); // ); } Widget _buildDataTableContent() { if (filteredData.isEmpty) { return const SizedBox( height: 50, child: Center(child: Text('No available data')), ); } final groupedData = _groupedEnquiries; return SingleChildScrollView( scrollDirection: Axis.vertical, // 👈 Only vertical scroll here child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...groupedData.entries.map((entry) { final status = entry.key; final items = entry.value; final isExpanded = groupExpanded[status] ?? true; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Group Header if (status != 'Proposal Accepted' && status != 'Proposal Rejected') ...[ Container( // width: double.infinity, width: MediaQuery.of(context).size.width, margin: EdgeInsets.fromLTRB(12, 4, 12, 4), child: _buildGroupHeader(status, items.length), ), ], // Group rows if (isExpanded && items.isNotEmpty) ...items.asMap().entries.map((itemEntry) { final index = itemEntry.key; final item = itemEntry.value; final startIndex = (currentPage - 1) * itemsPerPage; final sno = startIndex + index + 1; final id = int.tryParse(item['id'].toString()) ?? 0; final selectId = item['id'].toString() ?? '0'; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Data row Container( width: MediaQuery.of( context, ).size.width, // margin: EdgeInsets.all(10.0), margin: EdgeInsets.fromLTRB(12, 8, 12, 8), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Color(0xFFFFECF1F7)), // border: Border( // left: BorderSide(color: Color(0XFFE2E8F0)), // right: BorderSide(color: Color(0XFFE2E8F0)), // bottom: BorderSide(color: Color(0XFFE2E8F0)), // top: BorderSide(color: Color(0XFFE2E8F0)), // ), borderRadius: BorderRadius.circular(8), // boxShadow: [ // BoxShadow( // color: Color(0XFFE2E8F0), // blurRadius: 3, // spreadRadius: 0.2, // offset: const Offset(0, 1), // soft drop shadow // ), // ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ DataTable( border: TableBorder.all( color: Colors.transparent, width: 0, ), // border: TableBorder( // top: BorderSide.none, // left: BorderSide.none, // right: BorderSide.none, // bottom: BorderSide.none, // horizontalInside: BorderSide.none, // verticalInside: BorderSide.none, // ), headingRowHeight: 0, dividerThickness: 0.1, columnSpacing: 20.0, dataRowMinHeight: 40, columns: _buildDataColumns(), rows: [_buildDataRow(item, sno)], ), // Expanded content if (rowExpanded[id] ?? false) Container( // width: double.infinity, width: MediaQuery.of(context).size.width, decoration: BoxDecoration( // borderRadius: const BorderRadius.only( // topLeft: Radius.circular(25), // topRight: Radius.circular(25), // ), border: Border.all( color: Color(0xFFC7D2FE), ), color: Color(0xFFF8F9FF), ), padding: const EdgeInsets.symmetric( vertical: 14.0, horizontal: 8.0, ), child: _buildSubRow(context, selectId, id), ), ], ), ), ], ); }).toList(), if (isExpanded && items.isEmpty) if (status != 'Proposal Accepted' && status != 'Proposal Rejected') Padding( padding: EdgeInsets.all(5), child: Center( child: Text( 'No enquiries found', style: GoogleFonts.inter( fontSize: 12, color: Colors.grey, fontStyle: FontStyle.italic, ), ), ), ), ], ); }).toList(), ], ), ); } DataRow _buildDataRow(Map item, int sno) { final id = int.tryParse(item['id'].toString()) ?? 0; final selectId = item['id'].toString() ?? '0'; final bool isNewRow = int.tryParse(id.toString()) != null && int.parse(id.toString()) > 1000000000000; print('idPrimary - $idPrimary'); print('isNewRow - $isNewRow'); final editing = isEditingRow[id] ?? false; final controllers = rowControllers[id]; String status = item['status'] ?? '-'; Color bgColor = statusColors[status] ?? Color(0xFFF9FAFB); Color borderColor = statusBorderColors[status] ?? Color(0xFFE2E8F0); return DataRow( cells: [ DataCell( SizedBox( width: 70, child: Row( children: [ // Text(item['created_on'], style: _dataBold), Text( formatDateTimeForTable(item['created_on'], newLine: true), style: _dataBoldthm1, ), ], ), ), ), // Assigned To DataCell( SizedBox( // flex: 1, width: 110, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( item['assigned_to_name'] != null ? _truncateText(item['assigned_to_name'], 15) : '-', style: _dataBold, ), Text(_formatDate(item['updated_on']), style: _dataBoldthm4), ], ), ), ), // Agent Name DataCell( SizedBox( width: 100, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(item['agent_code'] ?? '-', style: _dataBold), Text( item['agent_name'] != null ? _truncateText(item['agent_name'], 20) : '-', style: _tableDataTimeStyle, ), ], ), ), ), // Insurer Name DataCell( SizedBox( width: 60, child: Text( item['insurer_short_name'] ?? '-', style: _dataBoldthm2, ), ), ), DataCell( SizedBox( width: 90, child: Tooltip( message: item['insured_name'], child: Text( item['insured_name'] != null ? _truncateText(item['insured_name'], 15) : '-', style: _dataBoldthm2, ), ), ), ), // Vehicle No DataCell( SizedBox( width: 95, child: Builder( builder: (buttonContext) => Material( color: Colors.white, // child: InkWell( // // hoverColor: Color(0xFFEAF6F4), // onTap: () async { // // ✅ Get overlay and button position RELATIVE to InkWell // final RenderBox button = // buttonContext.findRenderObject() as RenderBox; // final RenderBox overlay = // Overlay.of(buttonContext).context.findRenderObject() // as RenderBox; // // final Offset position = button.localToGlobal( // Offset.zero, // ancestor: overlay, // ); // // await showMenu( // context: buttonContext, // position: RelativeRect.fromLTRB( // position.dx, // position.dy + button.size.height, // overlay.size.width, // // overlay.size.width - position.dx, // 0, // ), // items: [ // PopupMenuItem( // child: Column( // mainAxisSize: MainAxisSize.min, // children: _buildPopupDownload_BTN( // buttonContext, // item, // item['id'], // item['reg_no'], // ), // ), // ), // ], // color: Colors.white, // ); // }, child: Tooltip( message: 'Click and Download Enquiry Files', // what appears on hover/long press waitDuration: const Duration(milliseconds: 500), // optional showDuration: const Duration(seconds: 2), // optional child: Container( padding: EdgeInsets.all(4.0), margin: EdgeInsets.symmetric(vertical: 3, horizontal: 2), decoration: BoxDecoration( color: Color(0xFFF9FAFB), border: Border.all(color: Color(0xFFE2E8F0)), borderRadius: BorderRadius.circular(5), ), child: Text(item['reg_no'] ?? '-', style: _dataBoldthm3), ), ), // child: Text(item['reg_no'] ?? '-', style: _dataBold), // ), ), ), ), ), DataCell( SizedBox( width: 70.0, child: Row( children: [ Tooltip( richMessage: WidgetSpan( child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( // color: Colors.white, color: Color(0xFFFFFCF2), borderRadius: BorderRadius.circular(6), // border: Border.all(color: Colors.blueGrey.shade100), // border: Border.all(color: Colors.grey.shade400), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, // mainAxisSize: MainAxisSize.min, children: [ _row("Email", item["email"]), _row("Mobile", item["mobile"]), _row("Remarks", item["remarks"]), ], ), ), ), padding: EdgeInsets .zero, // important (remove default tooltip padding) verticalOffset: 12, // distance from icon waitDuration: Duration(milliseconds: 500), showDuration: Duration(seconds: 2), decoration: BoxDecoration(), // remove default yellow box child: Icon( Icons.info_outline_rounded, size: 15, color: Colors.blue, ), ), IconButton( padding: EdgeInsets.zero, // remove outer padding constraints: BoxConstraints( // reduce clickable area minWidth: 24, minHeight: 24, ), icon: Icon( rowExpanded[id] == true ? Icons.edit_off_sharp : Icons.edit, color: Colors.blue, size: 15, ), tooltip: "Edit", onPressed: () { setState(() { isActionable = false; bool isOpen = rowExpanded[id] ?? false; rowExpanded.clear(); // close all if (!isOpen) { isActionable = true; rowExpanded[id] = true; // open only if previously closed } // rowExpanded[id] = !(rowExpanded[id] ?? false); }); handleEdit(item); }, ), Builder( builder: (buttonContext) => Material( color: Colors.white, child: InkWell( // hoverColor: Color(0xFFEAF6F4), onTap: () async { // ✅ Get overlay and button position RELATIVE to InkWell final RenderBox button = buttonContext.findRenderObject() as RenderBox; final RenderBox overlay = Overlay.of(buttonContext).context.findRenderObject() as RenderBox; final Offset position = button.localToGlobal( Offset.zero, ancestor: overlay, ); await showMenu( context: buttonContext, position: RelativeRect.fromLTRB( position.dx, position.dy + button.size.height, overlay.size.width, // overlay.size.width - position.dx, 0, ), items: [ PopupMenuItem( child: Column( mainAxisSize: MainAxisSize.min, children: [MultipleFileWidget(enqId: selectId)], ), ), ], color: Colors.white, ); }, child: Tooltip( message: 'Click and Upload / Download Enquiry Files', // what appears on hover/long press waitDuration: const Duration( milliseconds: 500, ), // optional showDuration: const Duration(seconds: 2), // optional child: Icon( Icons.file_upload_outlined, size: 15, color: Colors.green, ), ), ), ), ), ], ), ), ), // Assigned Date (formatted) // DataCell( // SizedBox( // width: 80, // child: Text( // // item['updated_on'] ?? '-', // formatDateTimeForTable(item['updated_on'], newLine: true), // style: _dataBold, // softWrap: true, // maxLines: 3, // ), // ), // ), // Premium // DataCell( // // (item['status'] == 'Proposal Created' || // // item['status'] == 'Proposal Rejected') // // ? SizedBox( // // width: 80, // // child: InkWell( // // onTap: () { // // // handle approval logic // // // // handleProposalAccept(context, item['id']); // // }, // // child: Text( // // 'Click To\nApprove', // // style: GoogleFonts.inter( // // color: Colors.green, // // fontSize: 11, // // fontWeight: FontWeight.w700, // // ), // // ), // // ), // // ) // // : // SizedBox( // width: 90, // child: Text( // item['premium_amount']?.toString() ?? '-', // style: _dataBold, // ), // ), // ), // // Payment Mode // DataCell( // // Material(child: buildPaymentMode(context)), // SizedBox( // // width: 80, // // child: Text(item['payment_mode_value'] ?? '-', style: _dataBold), // child: buildPaymentMode(context), // ), // ), DataCell( SizedBox( height: 30, width: 120, child: (item['status'] == 'Awaiting Proposal') ? Text('-') : Builder( builder: (cellContext) => DropdownSearch>( key: ValueKey(item['id']), selectedItem: () { try { return filteredPaymentModeData.firstWhere( (mode) => mode['id'].toString() == item['payment_mode_id'].toString(), orElse: () => {}, ); } catch (e) { return null; } }(), items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredPaymentModeData; } return filteredPaymentModeData.where((paymentMode) { return paymentMode['value'] .toString() .toLowerCase() .contains(filter.toLowerCase()); }).toList(); }, itemAsString: (val) => val['value'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 8, // smaller icon color: Colors.black45, // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) { // Check if selectedItem is null or empty map bool isEmpty = selectedItem == null || selectedItem.isEmpty || selectedItem['value'] == null; return Container( padding: EdgeInsets.zero, // padding: EdgeInsets.symmetric(horizontal: 2), alignment: Alignment.centerLeft, child: Text( isEmpty ? "Select Payment Mode" : selectedItem['value'].toString(), style: GoogleFonts.poppins( fontSize: 11, color: isEmpty ? Colors .grey .shade500 // Grey for hint : Colors.black, // Blue for selected // : Color(0XFF6366F1), // Blue for selected fontWeight: isEmpty ? FontWeight.w400 : FontWeight.w400, ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ); }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( // label: "Select Payment Mode", ).copyWith( filled: true, fillColor: Colors .white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( //color: Color(0xFFFFFFFF), color: Color(0xFFE2E8F0), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 1, vertical: 6, ), ), ), // 🔥 Use PopupProps.menu - works better in tables popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints( maxHeight: 300, minWidth: 200, ), menuProps: MenuProps( backgroundColor: Colors.white, elevation: 8, borderRadius: BorderRadius.circular(8), ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.all(8), filled: true, fillColor: Color(0xFFF8FAFC), hintText: "Search...", hintStyle: GoogleFonts.inter( fontSize: 11, color: Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide( color: Color(0xFFE2E8F0), ), ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( color: isSelected ? Colors.blue.withOpacity(0.1) : Colors.white, padding: EdgeInsets.symmetric( horizontal: 5, vertical: 5, ), child: Text( item['value'].toString(), style: GoogleFonts.inter( fontSize: 11, color: Colors.black87, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ); }, ), onChanged: (val) { dynamic id; if (val != null) { setState(() { item['payment_mode_id'] = val['id'].toString(); item['payment_mode'] = val['value'].toString(); id = val['id'].toString(); }); } handlePaymentMode(id, item['quotation_id']); }, ), ), ), ), DataCell( SizedBox( width: 120, height: 30, child: (item['status'] == 'Awaiting Proposal') ? Text('-') : Builder( builder: (cellContext) => DropdownSearch>( key: ValueKey(item['id']), selectedItem: () { try { return filteredInsuranceData.firstWhere( (mode) => mode['insurance_plan_type'] .toString() .trim() .toLowerCase() == item['insurance_plan_type'] .toString() .trim() .toLowerCase(), orElse: () => {}, ); } catch (e) { return null; } }(), items: (filter, infiniteScrollProps) async { if (filter.isEmpty) { return filteredInsuranceData; } return filteredInsuranceData.where((paymentMode) { return paymentMode['insurance_plan_type'] .toString() .toLowerCase() .contains(filter.toLowerCase()); }).toList(); }, itemAsString: (val) => val['insurance_plan_type'].toString(), compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], suffixProps: DropdownSuffixProps( // make sure the dropdown button is visible dropdownButtonProps: DropdownButtonProps( isVisible: true, padding: EdgeInsets.zero, // remove default padding constraints: const BoxConstraints( // shrink icon tap area minWidth: 12, minHeight: 12, ), iconSize: 8, // smaller icon color: Colors.black45, // icon: const Icon(Icons.arrow_drop_down), ), ), dropdownBuilder: (context, selectedItem) { // Check if selectedItem is null or empty map bool isEmpty = selectedItem == null || selectedItem.isEmpty || selectedItem['insurance_plan_type'] == null; return Container( padding: EdgeInsets.zero, // padding: EdgeInsets.symmetric(horizontal: 2), alignment: Alignment.centerLeft, child: Text( isEmpty ? "Select Insurer" : selectedItem['insurance_plan_type'] .toString(), style: GoogleFonts.poppins( fontSize: 11, color: isEmpty ? Colors .grey .shade500 // Grey for hint : Colors.black, // Blue for selected // : Color(0XFF6366F1), // Blue for selected fontWeight: isEmpty ? FontWeight.w400 : FontWeight.w400, ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ); }, decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( // label: "Select Payment Mode", ).copyWith( filled: true, fillColor: Colors .white, // 👈 makes the dropdown input white isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color(0xFFE2E8F0), width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( //color: Color(0xFFFFFFFF), color: Color(0xFFE2E8F0), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 1, vertical: 6, ), ), ), // 🔥 Use PopupProps.menu - works better in tables popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints( maxHeight: 300, minWidth: 200, ), menuProps: MenuProps( backgroundColor: Colors.white, elevation: 8, borderRadius: BorderRadius.circular(8), ), showSearchBox: true, searchFieldProps: TextFieldProps( autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.all(8), filled: true, fillColor: Color(0xFFF8FAFC), hintText: "Search...", hintStyle: GoogleFonts.inter( fontSize: 11, color: Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide(color: Color(0xFFE2E8F0)), ), ), ), itemBuilder: (context, item, isDisabled, isSelected) { return Container( color: isSelected ? Colors.blue.withOpacity(0.1) : Colors.white, padding: EdgeInsets.symmetric( horizontal: 5, vertical: 5, ), child: Text( item['insurance_plan_type'].toString(), style: GoogleFonts.inter( fontSize: 11, color: Colors.black87, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ); }, ), onChanged: (val) { dynamic id; if (val != null) { setState(() { // item['payment_mode_id'] = val['id'].toString(); item['insurance_plan_type'] = val['insurance_plan_type'].toString(); id = val['id'].toString(); }); } handleInsurancePlanTypeMode(id, item['quotation_id']); }, ), ), ), ), // DataCell( // buildPaymentMode(context), // // Builder( // // builder: (cellContext) => buildPaymentMode(cellContext), // // // SizedBox(width: 200, child: buildPaymentMode(cellContext)), // // ), // ), // Policy Number DataCell( SizedBox( height: 30, width: 90, child: (item['status'] == 'Awaiting Proposal') ? Text('-') : ThemedUploadField( // hintText: item['policy_pdf_file_name'] ?? "Upload Document", hintText: selectedFileNames ?? item['policy_pdf_file_name'] ?? "Upload Document", // txtheight: 30, padHorizontal: 4, padVertical: 5, fontSZ: 11, borderCirculr: 5, txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.18, borderColor: Color(0xFFE2E8F0), // highlightColor: Color(0xFF50A398), // backgroundColor: Color(0xFFEDF6F5), onFileSelected: (fileName, file) async { print("Picked file: $fileName (${file.size} bytes)"); if (!fileName.toLowerCase().endsWith('.pdf')) { ToastHelper.showErrorToast( context, 'Only PDF files are allowed.', ); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Only PDF files are allowed.'), backgroundColor: Colors.red, ), ); return; // Stop here } print("Picked file: $fileName (${file.size} bytes)"); setState(() { docUploadedFile = file; }); await uploadPolicyPDF( file: docUploadedFile, data: { "quotation_id": item["quotation_id"], "manager_id": managerId, "created_by": userId, "updated_by": userId, // "id": item["id"], // ONLY while updating }, ); // if (selectedBrokerName == 'Nhance') { // handleUploadPolicy(); // } }, ), ), ), DataCell( SizedBox( width: 120, child: Material( color: Colors.white, child: InkWell( // hoverColor: Color(0xFFEAF6F4), // onTap: null, onTap: () async { setState(() { isActionable = true; }); buildPolicyCreatedStatusActions( context, item['status'], item['id'], ); }, child: Tooltip( message: 'Click to View or Process Enquiry', // waitDuration: const Duration(milliseconds: 500), // optional showDuration: const Duration(seconds: 2), // optional child: Container( padding: EdgeInsets.symmetric(vertical: 2.0), margin: EdgeInsets.symmetric(vertical: 2), decoration: BoxDecoration( // color: Color(0xFFF9FAFB), // border: Border.all(color: Color(0xFFE2E8F0)), color: bgColor, border: Border.all(color: borderColor), borderRadius: BorderRadius.circular(5), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( item['status'] ?? '-', style: GoogleFonts.inter( fontSize: 10, color: Color(0XFF1e293b), // color: statusTextColors[status] ?? Colors.black, fontWeight: FontWeight.w500, ), // overflow: TextOverflow.clip, // ), ], ), ), ), // child: Text(item['status'] ?? '-', style: _dataBold), ), ), ), ), // DataCell( // SizedBox( // width: 120, // // child: Material( // color: Colors.white, // child: InkWell( // // hoverColor: Color(0xFFEAF6F4), // onTap: () async { // var status = item['status']; // var enqId = item['id']; // // final prefs = await SharedPreferences.getInstance(); // // // ✅ Remove old value (if any) // await prefs.remove('enqStaffDataId'); // // // ✅ Save the new id // await prefs.setString('enqStaffDataId', enqId.toString()); // ref.read(quotationStaffIdProvider.notifier).state = enqId; // buildStatusActions(context, status, enqId); // }, // child: Tooltip( // message: 'Click to View or Process Enquiry', // // waitDuration: const Duration(milliseconds: 500), // optional // showDuration: const Duration(seconds: 2), // optional // child: Container( // padding: EdgeInsets.symmetric(vertical: 2.0), // margin: EdgeInsets.symmetric(vertical: 2), // decoration: BoxDecoration( // // color: Color(0xFFF9FAFB), // // border: Border.all(color: Color(0xFFE2E8F0)), // color: bgColor, // border: Border.all(color: borderColor), // borderRadius: BorderRadius.circular(5), // ), // // child: Row( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Text( // item['status'] ?? '-', // style: GoogleFonts.inter( // fontSize: 10, // color: Color(0XFF1e293b), // // color: statusTextColors[status] ?? Colors.black, // fontWeight: FontWeight.w500, // ), // // overflow: TextOverflow.clip, // // ), // ], // ), // ), // ), // // child: Text(item['status'] ?? '-', style: _dataBold), // ), // ), // ), // ), ], ); } // Add this helper method to generate DataColumns List _buildDataColumns() { return [ DataColumn(label: Text('Received Date', style: _headerStyle)), DataColumn(label: Text('Assigned To *', style: _headerStyle)), DataColumn(label: Text('Partner *', style: _headerStyle)), // const DataColumn(label: Text('Broker *', style: _headerStyle)), DataColumn(label: Text('Insurer *', style: _headerStyle)), DataColumn(label: Text('Insured Name *', style: _headerStyle)), DataColumn(label: Text('Vehicle No *', style: _headerStyle)), DataColumn(label: Text('Action', style: _headerStyle)), // const DataColumn(label: Text('Assigned Date', style: _headerStyle)), // DataColumn(label: Text('Premium', style: _headerStyle)), DataColumn(label: Text('Payment Mode', style: _headerStyle)), DataColumn(label: Text('Plan Type', style: _headerStyle)), DataColumn(label: Text('Policy', style: _headerStyle)), DataColumn(label: Text('Status', style: _headerStyle)), ]; } // 6. Build table header Widget _buildTableHeader(bool isDesktop) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ _buildHeaderCell('Received Date', 110), _buildHeaderCell('Assigned To *', 130), _buildHeaderCell('Partner *', 100), // _buildHeaderCell('Broker *', 100), _buildHeaderCell('Insurer *', 90), _buildHeaderCell('Insured Name *', 110), _buildHeaderCell('Vehicle No *', 90), _buildHeaderCell('Action', 80), // _buildHeaderCell('Assigned Date', 100), // _buildHeaderCell('Premium', 100), _buildHeaderCell('Payment Mode', 130), _buildHeaderCell('Plan Type', 130), _buildHeaderCell('Policy', 120), _buildHeaderCell('Status', 100), ], ), ); } Widget _buildHeaderCell(String label, double width) { return Container( padding: EdgeInsets.symmetric(horizontal: 4), child: SizedBox( width: width, child: Text( label, style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), // color: Colors.black87, ), ), ), ); } Widget _row(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisSize: MainAxisSize.min, // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( width: 70, child: Text( "$label ", style: GoogleFonts.poppins( color: Colors.black, fontSize: 10, fontWeight: FontWeight.w500, ), ), ), SizedBox( width: 180, child: Text( value ?? '-', softWrap: true, maxLines: 5, style: GoogleFonts.poppins( color: Colors.black, fontSize: 10, fontWeight: FontWeight.w400, ), ), ), ], ), ); } static final _dataBold = GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black, // color: Color(0xFF000000), ); static final _dataBoldthm1 = GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w500, color: Colors.black, // color: Color(0xFF000000), ); static final _dataBoldthm2 = GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w500, // color: Colors.black, color: Color(0XFF334155), ); static final _dataBoldthm3 = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w500, // color: Colors.black, color: Color(0XFF334155), ); static final _dataBoldthm4 = GoogleFonts.inter( fontSize: 9, fontWeight: FontWeight.w500, // color: Colors.black, color: Color(0XFF334155), ); final _tableDataTimeStyle = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w400, color: Colors.black, // color: Color(0XFF94A3B8), ); final _subLabelTimeStyle = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w400, color: Color(0XFF6366F1), ); static final _dataBoldsmall = GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); static final _dataSub = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); static final _headerStyle = GoogleFonts.inter( // color: Colors.black, color: Color(0xFF1E293B), fontWeight: FontWeight.w500, ); static final _cardheaderStyle = GoogleFonts.inter( color: Colors.black, fontWeight: FontWeight.w600, fontSize: 12, ); static final TextStyle _textStyle = GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w600, ); static const _cardBodyStyle = TextStyle( color: Color(0xFF545454), fontWeight: FontWeight.w400, fontSize: 12, ); String _truncateText(String text, int limit) { if (text.length <= limit) return text; return '${text.substring(0, limit)}...'; } String _wrapText(String text, int chunkSize) { if (text.isEmpty) return '-'; final buffer = StringBuffer(); for (int i = 0; i < text.length; i += chunkSize) { int end = (i + chunkSize < text.length) ? i + chunkSize : text.length; buffer.writeln(text.substring(i, end)); // adds '\n' after each chunk } return buffer.toString().trim(); } String formatDateTimeForTable(String? input, {bool newLine = false}) { if (input == null || input.trim().isEmpty) return '-'; final s = input.trim(); DateTime dt; try { // Case 1: ISO-like (yyyy-MM-dd ...), DateTime.parse will handle if (RegExp(r'^\d{4}-\d{2}-\d{2}').hasMatch(s)) { dt = DateTime.parse(s); } // Case 2: day-month-year at start (e.g. 13-11-2025 07:28 AM or 13-11-2025 07:28) else if (RegExp(r'^\d{2}-\d{2}-\d{4}').hasMatch(s)) { // Try with am/pm (hh:mm a) if (RegExp(r'\b(am|pm)\b', caseSensitive: false).hasMatch(s)) { dt = DateFormat('dd-MM-yyyy hh:mm a').parse(s); } // Try 24-hour time "dd-MM-yyyy HH:mm" or just date "dd-MM-yyyy" else if (RegExp(r'^\d{2}-\d{2}-\d{4}\s+\d{1,2}:\d{2}').hasMatch(s)) { dt = DateFormat('dd-MM-yyyy HH:mm').parse(s); } else { dt = DateFormat('dd-MM-yyyy').parse(s); } } // Case 3: fallback — try parse with DateTime.parse (may throw) else { dt = DateTime.parse(s); } final dateStr = DateFormat('dd-MM-yyyy').format(dt); final timeStr = DateFormat('hh:mm a').format(dt); // e.g. 07:28 AM return newLine ? '$dateStr\n$timeStr' : '$dateStr | $timeStr'; } catch (e) { // If parsing fails, return original string as fallback (or '-' if empty) return s.isNotEmpty ? s : '-'; } } }