import 'dart:ui'; import 'dart:async'; 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:shared_preferences/shared_preferences.dart'; import '../../../../core/routing/routes.dart'; import '../../../../core/services/api_service.dart'; import 'package:universal_html/html.dart' as html; import '../../../../data/utils/Pagination.dart'; import '../../../core/config/env.dart'; import '../../../data/services/auth_service.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/date_field_theme.dart'; import '../../themes/indicators/search_field_theme.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 '../../widgets/custom_action_popup.dart'; import '../staff/Enquiry/quotationPopUpAccptReject.dart'; import '../staff/assignStaff.dart'; class EnquiryListHandler extends ConsumerStatefulWidget { const EnquiryListHandler({super.key}); @override ConsumerState createState() => EnquiryHandlerState(); } class EnquiryHandlerState extends ConsumerState { int currentPage = 1; int itemsPerPage = 10; late ApiService apiService; dynamic userId; dynamic idPrimary; dynamic managerId; dynamic dashboardKey; dynamic SelectedStatus; dynamic SelectedStaffId; dynamic handlerId; String? _token; final _formKey = GlobalKey(); Map fieldErrors = {}; // List> dataVal = []; List> getStaffData = []; List> originalData = []; List> filteredData = []; bool isLoading = false; dynamic roleId; bool _showFilterRow = false; Timer? _staleAssignedBlinkTimer; bool _isBlinkPhaseOn = false; Map controllers = {}; List tabHeader = [ 'startDate', 'endDate', 'name', 'email', 'mobile', 'code', 'address', 'regNo', 'remarks', 'enquiry_created_on', // 👈 Add this line ]; Map isEditingRow = {}; Map> rowControllers = {}; //---------------------- Enquiry TAB Initializations Starts -------------------------------- // List> getVehicleTypeData = []; List> filteredVechicleData = []; List> getAgentListData = []; List> filteredAgentData = []; List> getInsurersData = []; List> filteredInsurersData = []; String? selectedVehicleType; String? selectedInsurer; String? selectedAgent; PlatformFile? docUploadedRCFile; PlatformFile? docUploadedIDProof; PlatformFile? docUploadedPrevPolicy; 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')) ? 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, "enquiry_created_on" : controllers["enquiry_created_on"]?.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>>(); //---------------------- 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; //---------------------- Assign Staff Ends -------------------------------- // //---------------------- Enquiry TAB Initializations Ends -------------------------------- // @override void initState() { super.initState(); apiService = ApiService(); 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(); getBroker(); _staleAssignedBlinkTimer = Timer.periodic( const Duration(milliseconds: 700), (_) { if (!mounted) return; setState(() { _isBlinkPhaseOn = !_isBlinkPhaseOn; }); }, ); } @override void dispose() { _staleAssignedBlinkTimer?.cancel(); for (final controller in controllers.values) { controller.dispose(); } for (final rowControlMap in rowControllers.values) { for (final controller in rowControlMap.values) { controller.dispose(); } } super.dispose(); } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } 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 ?? '', ); } 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 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 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']; /* * Some APIs do not return from_date/to_date in the payload. * Keep UI date fields stable by falling back to the request dates. */ final fromDate = (response['from_date'] ?? '').toString().trim().isNotEmpty ? response['from_date'].toString() : (fromDt?.toString() ?? ''); final toDate = (response['to_date'] ?? '').toString().trim().isNotEmpty ? response['to_date'].toString() : (toDt?.toString() ?? ''); 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['enquiry_created_on']) ?? '-').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, // width: MediaQuery.of(context).size.width * 0.4, // height: MediaQuery.of(context).size.height * 0.4, // width: MediaQuery.of(context).size.width * 0.7, // height: MediaQuery.of(context).size.height * 0.8, 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']; // Handle the date formatting for the controller if (item['enquiry_created_on'] != null) { DateTime dt = DateTime.parse(item['enquiry_created_on'].toString()); controllers['enquiry_created_on']!.text = DateFormat('dd-MM-yyyy').format(dt); } else { controllers['enquiry_created_on']!.text = ''; } setState(() {}); // setState(() { // isEditingRow[id] = true; // rowControllers[id] = { // 'agent_name': TextEditingController(text: item['agent_name'] ?? ''), // 'insurer_name': TextEditingController(text: item['insurer_name'] ?? ''), // 'reg_no': TextEditingController(text: item['reg_no'] ?? ''), // 'premium_amount': TextEditingController( // text: item['premium_amount'] ?? '', // ), // 'payment_mode': TextEditingController(text: item['payment_mode'] ?? ''), // }; // }); } void handleSave(int id) { // final controllers = rowControllers[id]; // if (controllers == null) return; if (!_validateRequiredFields()) return; setState(() { // final index = getStaffData.indexWhere( // (e) => e['id'].toString() == id.toString(), // ); // if (index != -1) { // getStaffData[index] = { // ...getStaffData[index], // 'agent_name': controllers['agent_name']?.text, // 'insurer_name': controllers['insurer_name']?.text, // 'reg_no': controllers['reg_no']?.text, // 'premium_amount': controllers['premium_amount']?.text, // 'payment_mode': controllers['payment_mode']?.text, // }; // } // // if (index != -1) { // print("EDITField - ${getStaffData[index]}"); // getStaffData[index] = { // ...getStaffData[index], // 'agent_id': selectedAgent, // "name": controllers["name"]?.text, // "mobile": controllers["mobile"]?.text, // "email": controllers["email"]?.text, // "reg_no": controllers["regNo"]?.text, // "vehicle_type_id": selectedVehicleType, // }; // } isEditingRow[id] = false; rowControllers.remove(id); }); handleEnqTabSave(); } void handleCancel(int id) { setState(() { // 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 SizedBox( height: 35, width: MediaQuery.of(context).size.width * 0.08, child: DropdownSearch>( key: dropDownSelectStaffKey, // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, selectedItem: selectedVehicle, items: (filter, infiniteScrollProps) { return filteredStaffDataEnqAsgn; }, 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; }, 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: Colors.black, width: 0.1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Colors.black, width: 0.1), ), contentPadding: EdgeInsets.symmetric( horizontal: 2, vertical: 6, ), ), ), 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, ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: MenuProps( backgroundColor: Colors.white, // 👈 sets dropdown background to white ), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: " IO 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) { Map? selectedInsurerd = filteredInsurersData.firstWhere( (item) => item['id'].toString() == selectedInsurer, orElse: () => {}, ); return Container( height: 35, width: MediaQuery.of(context).size.width * 0.08, child: DropdownSearch>( key: dropDownKeyInsurerEnqAsgn, selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, items: (filter, infiniteScrollProps) { return filteredInsurersData; }, 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; }, 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 Insurer", ).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: 2, 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( decoration: InputDecoration( 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: 35, 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; }, 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: 2, 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( decoration: InputDecoration( 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) { Map? selectedAgntName = filteredAgentData.firstWhere( (item) => item['id'].toString() == selectedAgent, orElse: () => {}, ); return SizedBox( width: MediaQuery.of(context).size.width * 0.08, height: 35, child: DropdownSearch>( key: dropDownKeyAgent, selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, items: (filter, infiniteScrollProps) { return filteredAgentData; }, 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; }, dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['name'].toString() : "", // ✅ FIXED style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Partner", ).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: 1, // 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( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Partner...", 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: 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 buildEnquiryCreatedOn(BuildContext context) { // return Container( // width: 150, // Set a specific width for the table cell // child: ThemedDateField( // controller: controllers['enquiry_created_on'], // hintText: "Select Date", // borderColor: Colors.black, // // Pass the date back to your item or state // onDateSelected: (DateTime pickedDate) { // setState(() { // // Format it back to your backend requirement if needed // String formatted = DateFormat('dd-MM-yyyy').format(pickedDate); // controllers['enquiry_created_on']!.text = formatted; // }); // }, // ), // ); // } // Widget buildEnquiryCreatedOn(BuildContext context) { // return SizedBox( // width: 140, // Adjust width to fit the column // height: 35, // Standardized height for your inline edit row // child: ThemedDateField( // controller: controllers['enquiry_created_on'], // hintText: "Select Date", // borderColor: Colors.black, // onDateSelected: (DateTime pickedDate) { // setState(() { // // Store only the date part in the controller // String formatted = DateFormat('dd-MM-yyyy').format(pickedDate); // controllers['enquiry_created_on']!.text = formatted; // }); // }, // ), // ); // } Widget buildEnquiryCreatedOn(BuildContext context) { return SizedBox( height: 35, width: 140, // Give it a fixed width so it doesn't disappear in the table child: ThemedDateField( controller: controllers['enquiry_created_on'], hintText: "Select Date", borderColor: Colors.black, onDateSelected: (DateTime pickedDate) { setState(() { String formatted = DateFormat('dd-MM-yyyy').format(pickedDate); controllers['enquiry_created_on']!.text = formatted; }); }, ), ); } Widget buildVehicleNumber(BuildContext context) { return ThemedFormInlineField( controller: controllers['regNo']!, borderColor: Colors.black, 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) { return ThemedFormInlineField( controller: controllers['name']!, // errorText: fieldErrors['name'], borderColor: Colors.black, 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: 35, 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 dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem != null ? selectedItem['vehicle_type'].toString() : "", // ✅ FIXED style: GoogleFonts.poppins(fontSize: 11, color: Colors.black), 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( decoration: InputDecoration( filled: true, fillColor: Colors.white, hintText: "Search Vehicle Type...", 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['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 ThemedFormInlineField( controller: controllers['email']!, borderColor: Colors.black, 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.26, ); } Widget buildPhNumber(BuildContext context) { return ThemedFormInlineField( controller: controllers['mobile']!, borderColor: Colors.black, 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 ThemedFormField( // maxLength: 500, enableBorderWidth: 0.1, controller: controllers['remarks']!, borderColor: Colors.black, verticalPad: 8, horizonalPad: 10, isdense: true, // keyboardType: TextInputType.multiline, txtwidth: ResponsiveLayout.isMobile(context) ? null : MediaQuery.of(context).size.width * 0.1, ); } 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 = ThemedUploadField( hintText: hintText ?? "Upload Document", txtwidth: width, padVertical: 6, padHorizontal: 8, 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 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 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 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 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', ); } 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"); } } //---------------------- Enquiry TAB Logins End -------------------------------- // @override Widget build(BuildContext context) { return MainLayout( title: "Enquiries", body: SelectionArea( child: Container( // color: Colors.yellow.shade50, width: MediaQuery.of(context).size.width, padding: EdgeInsets.all(3.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ // Container( // height: 30, // width: MediaQuery.of(context).size.width, // child: GestureDetector( // onTap: () { // context.go(AppRoutes.dashboard); // }, // child: Row( // crossAxisAlignment: CrossAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.start, // children: [ // Tooltip( // message: 'Back', // child: IconButton( // icon: const Icon(Icons.arrow_left_sharp, size: 25), // onPressed: () async { // context.go(AppRoutes.dashboard); // final prefs = await SharedPreferences.getInstance(); // await prefs.remove('dashboardKeyProvider'); // await prefs.remove('dashboardStatusProvider'); // await prefs.remove('dashboardStaffIdProvider'); // }, // splashRadius: 18, // padding: const EdgeInsets.all(4), // hoverColor: Colors.black12, // constraints: const BoxConstraints(), // ), // ), // const SizedBox(width: 5), // spacing between icon and text // Text( // "Enquiries", // style: TextStyle( // fontSize: 18, // fontWeight: FontWeight.w600, // ), // ), // ], // ), // ), // ), // 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, // 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; }); }, ), ), ], ), ), ), ); } Widget _buildContent(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // if (ResponsiveLayout.isMobile(context)) ...[ // Container( // padding: EdgeInsets.all(8.0), // color: Color(0xffD9EBE8), // child: DateFilterRow( // key: ValueKey(SelectedStatus ?? ''), // onFilterStaff: (val) { // print('Selected Filterd STAFF Id - $val'); // SelectedStaffId = val; // }, // selectedStaffId: SelectedStaffId, // role: roleId, // id: userId, // selectedStatusVal: SelectedStatus, // startController: controllers['startDate']!, // endController: controllers['endDate']!, // formKey: _formKey, // isMobile: ResponsiveLayout.isMobile(context), // onStatusChanged: (val) { // SelectedStatus = val; // update parent // }, // // onFilter: () { // // call your filter logic // filterDateRange(); // }, // onRefresh: () { // // call your refresh logic // refrshfilterDateRange(); // }, // ), // ), // ], Container( // height: 40, // color: Colors.pink, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Container( // height: 30, // width: MediaQuery.of(context).size.width, child: GestureDetector( onTap: () { context.go(AppRoutes.dashboard); }, child: Row( // crossAxisAlignment: CrossAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.start, children: [ Tooltip( message: 'Back', child: IconButton( icon: const Icon(Icons.arrow_left_sharp, size: 25), onPressed: () async { context.go(AppRoutes.dashboard); final prefs = await SharedPreferences.getInstance(); await prefs.remove('dashboardKeyProvider'); await prefs.remove('dashboardStatusProvider'); await prefs.remove('dashboardStaffIdProvider'); }, splashRadius: 18, padding: const EdgeInsets.all(4), hoverColor: Colors.black12, constraints: const BoxConstraints(), ), ), const SizedBox(width: 5), // spacing between icon and text Text( "Enquiries", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), ], ), ), ), 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, ), tooltip: _showFilterRow ? 'Hide Filters' : 'Show Filters', onPressed: () { setState(() { _showFilterRow = !_showFilterRow; }); }, ), Spacer(), ThemedSearchField( hintText: 'Search', backgroundColor: Color(0xFFF6F8F8), onChanged: filterData, txtHeight: 35, controller: _searchStaffController, txtwidth: ResponsiveLayout.isMobile(context) ? MediaQuery.of(context).size.width * 0.7 : MediaQuery.of(context).size.width * 0.13, ), ResponsiveLayout.isMobile(context) ? Spacer() : SizedBox(width: 10), ExportBtn( sheetName: "Enquiry", fileName: "Enquiry_list", data: filteredData, txt: !ResponsiveLayout.isMobile(context) ? true : false, displayHeaders: [ // "Received Date & Time", "Received Date", // "Created Date & Time", // "Enquiry Created Date", "Partner", "Assigned To", "Insurer", "Vehicle.No.", "Insured Name", "Assigned Date & Time", "Premium", "Payment Mode", "Policy Number", "Status", ], keys: [ // "created_on", "enquiry_created_on", "agent_name", "assigned_to_name", "insurer_name", "reg_no", "insured_name", "updated_on", "premium_amount", "payment_mode", "policy_number", "status", ], ), SizedBox(width: 10), GestureDetector( onTap: () async { // final prefs = await SharedPreferences.getInstance(); // // await prefs.remove('enqAgentDataId'); // ref.read(enquiryIdProvider.notifier).state = null; // context.go(AppRoutes.tabEnquiry); // print('Export'); // Generate a unique ID for the new row final newId = DateTime.now().millisecondsSinceEpoch; // Clear all existing form controllers for (String field in tabHeader) { controllers[field]?.clear(); } // Reset dropdown selections selectedVehicleType = null; selectedAgent = null; selectedInsurer = null; // Reset file uploads docUploadedRCFile = null; docUploadedIDProof = null; docUploadedPrevPolicy = null; selectedRCFile = null; selectedIdProof = null; selectedPrevPolicy = null; setState(() { // Create a new empty row data final newRow = { 'id': newId.toString(), // Keep as String for consistency 'enquiry_created_on': '', 'agent_name': '', 'agent_id': '', 'assigned_to_name': '', 'insurer_name': '', 'insured_name': '', 'reg_no': '', 'vehicle_type': '', 'vehicle_type_id': '', 'email': '', 'mobile': '', 'remarks': '', 'updated_on': '', 'premium_amount': '', 'payment_mode': '', 'policy_number': '', 'status': '', }; // Insert at the beginning of ALL lists getStaffData.insert(0, newRow); originalData.insert(0, newRow); filteredData.insert(0, newRow); // Set to editing mode immediately isEditingRow[newId] = true; // Store the ID as string idPrimary = newId.toString(); selectedId = newId.toString(); // Reset to first page to see the new row currentPage = 1; }); }, child: Container( padding: EdgeInsets.all(8.0), decoration: BoxDecoration( color: Color(0xFF425B5B), borderRadius: BorderRadius.circular(8.0), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Tooltip( message: 'Raise Enquiry', child: Icon(Icons.add, color: Colors.white, size: 15), ), // if (!ResponsiveLayout.isMobile(context)) ...[ // SizedBox(width: 10), // Text( // 'Raise Enquiry', // style: GoogleFonts.inter( // color: Colors.white, // fontWeight: FontWeight.w600, // fontSize: 14, // ), // ), // ], ], ), ), ), ], ), ), // SizedBox(height: 10), // Container( // decoration: BoxDecoration( // color: const Color(0xFFEDF6F5), // borderRadius: BorderRadius.circular(6), // ), // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), // child: Row( // children: const [ // Expanded(flex: 1, child: Text('S.No', style: _headerStyle)), // Expanded( // flex: 2, // child: Text('Received Date', style: _headerStyle), // ), // Expanded(flex: 2, child: Text('Partner *', style: _headerStyle)), // Expanded( // flex: 2, // child: Text('Assigned To *', style: _headerStyle), // ), // Expanded(flex: 2, child: Text('Broker *', style: _headerStyle)), // Expanded(flex: 3, child: Text('Insurer *', style: _headerStyle)), // Expanded( // flex: 3, // child: Text('Insured Name *', style: _headerStyle), // ), // Expanded( // flex: 2, // child: Text('Vehicle No *', style: _headerStyle), // ), // Expanded( // flex: 2, // child: Text('Vehicle Type *', style: _headerStyle), // ), // Expanded(flex: 2, child: Text('Email', style: _headerStyle)), // Expanded(flex: 2, child: Text('Mobile', style: _headerStyle)), // Expanded(flex: 2, child: Text('Documents', style: _headerStyle)), // Expanded(flex: 2, child: Text('Remarks', style: _headerStyle)), // Expanded( // flex: 2, // child: Text('Assigned Date', style: _headerStyle), // ), // Expanded(flex: 2, child: Text('Premium', style: _headerStyle)), // Expanded( // flex: 2, // child: Text('Payment Mode', style: _headerStyle), // ), // Expanded( // flex: 2, // child: Text('Policy Number', style: _headerStyle), // ), // Expanded(flex: 2, child: Text('Status', style: _headerStyle)), // Expanded(flex: 1, child: Text('Action', style: _headerStyle)), // ], // ), // ), ResponsiveLayout.isMobile(context) ? _buildDataTable(context) : Expanded(child: _buildDataTable(context)), ], ); } List _buildPopupDownload(BuildContext context, selectId, editing) { 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), ], ), ) : Row( children: [ buildDocRC(context, selectId), SizedBox(width: 3), buildDocIdProof(context, selectId), SizedBox(width: 3), buildDocPrevPolicy(context, selectId), ], ), ]; } Widget _buildDataTable(BuildContext context) { if (filteredData.isEmpty) { return const SizedBox( height: 50, child: Center(child: Text('No available data')), ); } final sortedData = [..._paginatedData]; final startIndex = (currentPage - 1) * itemsPerPage; final isDesktop = !ResponsiveLayout.isMobile(context); // Build both-direction scrollable DataTable return LayoutBuilder( builder: (context, constraints) { double minWidth = constraints.maxWidth < 1300 ? 1300 : constraints.maxWidth; return ScrollConfiguration( behavior: const MaterialScrollBehavior().copyWith( dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch}, ), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: ConstrainedBox( constraints: BoxConstraints(minWidth: minWidth), child: DataTable( headingRowColor: WidgetStatePropertyAll(Color(0xFFEDF6F5)), dividerThickness: 0.5, headingRowHeight: 30, columnSpacing: isDesktop ? 20.0 : 16.0, border: TableBorder( horizontalInside: BorderSide( width: 0.5, color: Colors.grey.shade200, ), ), columns: const [ // DataColumn(label: Text('S.No', style: _headerStyle)), DataColumn( label: Text('Received Date', style: _headerStyle), ), DataColumn(label: Text('Partner *', style: _headerStyle)), DataColumn( label: Text('Assigned To *', style: _headerStyle), ), 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('Vehicle Type *', style: _headerStyle), ), DataColumn(label: Text('Email', style: _headerStyle)), DataColumn(label: Text('Mobile', style: _headerStyle)), DataColumn(label: Text('Documents', style: _headerStyle)), DataColumn(label: Text('Remarks', style: _headerStyle)), DataColumn(label: Text('Action', style: _headerStyle)), DataColumn( label: Text('Assigned Date', style: _headerStyle), ), DataColumn(label: Text('Premium', style: _headerStyle)), DataColumn( label: Text('Payment Mode', style: _headerStyle), ), DataColumn( label: Text('Policy Number', style: _headerStyle), ), DataColumn(label: Text('Status', style: _headerStyle)), ], rows: sortedData.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; final sno = startIndex + index + 1; return _buildDataRow(item, sno); }).toList(), ), ), ), ), ); }, ); } DataRow _buildDataRow(Map item, int sno) { final id = int.tryParse(item['id'].toString()) ?? 0; final selectId = item['id'].toString() ?? '0'; print('idPrimary - $idPrimary'); final editing = isEditingRow[id] ?? false; final controllers = rowControllers[id]; final shouldHighlight = _shouldHighlightAssignedRow(item); return DataRow( color: shouldHighlight ? WidgetStatePropertyAll( _isBlinkPhaseOn ? const Color(0xFFFFF3E0) : const Color(0xFFFFFFFF), ) : null, cells: _decorateCellsForStaleAssigned([ // S.No. // DataCell(Text('$sno', style: _dataBold)), // Created On // DataCell( // Row( // children: [ // // Text(item['created_on'], style: _dataBold), // Text( // formatDateTimeForTable(item['created_on'], newLine: true), // style: _dataBold, // ), // ], // ), // ), // Enquiry Created On DataCell( editing ? buildEnquiryCreatedOn(context) : Text(item['enquiry_created_on'] ?? '-', style: _dataBold), ), // Agent Name DataCell( editing ? buildAgentName(context) : Text(item['agent_name'] ?? '-', style: _dataBold), ), // Assigned To DataCell( editing ? buildSelectStaffMem(context) : Text(item['assigned_to_name'] ?? '-', style: _dataBold), ), DataCell( editing ? buildBroker(context) : Text(item['broker_name'] ?? '-', style: _dataBold), ), // Insurer Name DataCell( editing ? buildInsurer(context) : Text(item['insurer_short_name'] ?? '-', style: _dataBold), ), DataCell( editing ? buildInsuredName(context) : Text(item['insured_name'] ?? '-', style: _dataBold), ), // Vehicle No DataCell( editing ? buildVehicleNumber(context) : Text(item['reg_no'] ?? '-', style: _dataBold), ), DataCell( editing ? buildVehicleType(context) : Text( _wrapText(item['vehicle_type'] ?? '-', 13), style: _dataBold, softWrap: true, ), ), DataCell( editing ? buildEmail(context) : Text(item['email'] ?? '-', style: _dataBold), ), DataCell( editing ? buildPhNumber(context) : Text(item['mobile'] ?? '-', style: _dataBold), ), DataCell( Builder( builder: (buttonContext) => Material( color: Colors.white, child: InkWell( 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, editing, ), ), ), ], color: Colors.white, ); }, child: Tooltip( message: editing ? 'Click To Upload Documents' : 'Click To View Documents', waitDuration: const Duration(milliseconds: 500), showDuration: const Duration(seconds: 2), child: Icon( editing ? Icons.upload_file : Icons.download_for_offline, color: editing ? Colors.blue : Colors.green, size: 22, ), ), ), ), ), ), DataCell( editing ? buildRemarks(context) : Tooltip( message: item['remarks'] ?? '-', // full content on hover waitDuration: const Duration(milliseconds: 400), showDuration: const Duration(seconds: 2), child: Text( _truncateText(item['remarks'] ?? '-', 10), style: _dataBold, overflow: TextOverflow.ellipsis, ), ), ), // Actions DataCell( editing ? Row( children: [ IconButton( icon: const Icon(Icons.check, color: Colors.green), tooltip: "Save", onPressed: () => handleSave(id), ), IconButton( icon: const Icon(Icons.close, color: Colors.red), tooltip: "Cancel", onPressed: () => handleCancel(id), ), ], ) : Row( children: [ // if (roleId == 'manager') ...[ // if (item['status'] == 'Awaiting Proposal') // Tooltip( // message: "Assign Staff", // child: IconButton( // icon: Icon(Icons.assignment_ind_outlined, size: 15), // onPressed: () { // handleStaff( // context, // item, // item['id'], // item['reg_no'], // ); // }, // splashRadius: 5, // hoverColor: Colors.black12, // padding: const EdgeInsets.all(4), // constraints: const BoxConstraints(), // ), // ), // ], IconButton( icon: const Icon(Icons.edit, color: Colors.blue), tooltip: "Edit", onPressed: () => handleEdit(item), ), // IconButton( // icon: const Icon(Icons.delete, color: Colors.red), // tooltip: "Delete", // onPressed: () => setState(() { // getStaffData.removeWhere((e) => e['id'] == item['id']); // }), // ), ], ), ), // Assigned Date (formatted) DataCell( Text( // item['updated_on'] ?? '-', formatDateTimeForTable(item['updated_on'], newLine: true), style: _dataBold, softWrap: true, maxLines: 3, ), ), // Premium DataCell( // ((item['premium_amount'] != null) && // (item['premium_amount'] != '') && // ((item['status'] != 'Awaiting Proposal') || // (item['status'] == 'Proposal Created') || // (item['status'] == 'Proposal Rejected'))) (item['status'] == 'Proposal Created' || item['status'] == 'Proposal Rejected') ? 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, ), ), ) : Text( item['premium_amount']?.toString() ?? '-', style: _dataBold, ), ), // Payment Mode DataCell(Text(item['payment_mode'] ?? '-', style: _dataBold)), // Policy Number DataCell( Text( item['policy_number'] ?? '-', style: _dataBold, softWrap: true, maxLines: 3, ), ), // Status DataCell(Text(item['status'] ?? '-', style: _dataBold)), ], shouldHighlight), ); } List _decorateCellsForStaleAssigned( List cells, bool shouldHighlight, ) { if (!shouldHighlight) return cells; final borderColor = _isBlinkPhaseOn ? const Color(0xFFE65100) : const Color(0x00000000); return cells .map( (cell) => DataCell( Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 3), decoration: BoxDecoration( border: Border.all(color: borderColor, width: 1.3), borderRadius: BorderRadius.circular(2), ), child: cell.child, ), placeholder: cell.placeholder, showEditIcon: cell.showEditIcon, onTap: cell.onTap, onDoubleTap: cell.onDoubleTap, onLongPress: cell.onLongPress, onTapDown: cell.onTapDown, onTapCancel: cell.onTapCancel, ), ) .toList(); } bool _shouldHighlightAssignedRow(Map item) { final rawStatus = (item['enquiry_status'] ?? '').toString().trim(); if (rawStatus.isEmpty) return false; final normalizedStatus = rawStatus.toLowerCase(); final isAssignedStatus = normalizedStatus == 'assigned' || (normalizedStatus.contains('assigned') && !normalizedStatus.contains('to be assigned')); if (!isAssignedStatus) return false; final assignedAt = _parseAssignedDateTime(item); if (assignedAt == null) return false; return DateTime.now().difference(assignedAt) >= const Duration(minutes: 30); } DateTime? _parseAssignedDateTime(Map item) { final rawDate = [ item['assigned_to_datetime'], item['assigned_datetime'], item['assigned_on'], item['updated_on'], ].firstWhere( (value) => value != null && value.toString().trim().isNotEmpty, orElse: () => null, ); if (rawDate == null) return null; final value = rawDate.toString().trim(); try { return DateTime.parse(value); } catch (_) {} final parsers = [ DateFormat('dd-MM-yyyy hh:mm a'), DateFormat('dd-MM-yyyy HH:mm'), DateFormat('yyyy-MM-dd HH:mm:ss'), DateFormat('yyyy-MM-dd HH:mm'), ]; for (final parser in parsers) { try { return parser.parse(value); } catch (_) { // try next parser } } return null; } static final _dataBold = TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.black, // color: Color(0xFF000000), ); final _tableDataTimeStyle = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w400, ); static final _dataBoldsmall = TextStyle( fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xFF000000), ); static final _dataSub = TextStyle( fontSize: 10, fontWeight: FontWeight.w300, color: Color(0xFF585757), ); static const _headerStyle = TextStyle( color: Colors.black, fontWeight: FontWeight.w500, ); static const _cardheaderStyle = TextStyle( color: Colors.black, fontWeight: FontWeight.w600, fontSize: 12, ); static final TextStyle _textStyle = TextStyle( 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 : '-'; } } }