From 7239492b286f3d9b60e93e0c4a2a358df3fef38a Mon Sep 17 00:00:00 2001 From: venbaittech Date: Fri, 12 Dec 2025 17:06:06 +0530 Subject: [PATCH] enquiry_list_optimization --- .../staff/Enquiry/enquiry_inline_SubRow.dart | 1255 ++++ .../staff/Enquiry/enquiry_inline_list.dart | 4617 ++++---------- .../Enquiry/enquiry_inline_list_dec11.dart | 5520 +++++++++++++++++ .../Enquiry/enquiry_inline_list_dec12.dart | 3282 ++++++++++ 4 files changed, 11202 insertions(+), 3472 deletions(-) create mode 100644 lib/presentation/screens/staff/Enquiry/enquiry_inline_SubRow.dart create mode 100644 lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec11.dart create mode 100644 lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec12.dart diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_SubRow.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_SubRow.dart new file mode 100644 index 0000000..095ca45 --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_SubRow.dart @@ -0,0 +1,1255 @@ +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../../core/services/api_service.dart'; +import '../../../../data/services/auth_service.dart'; +import '../../../../data/utils/toastNotification.dart'; +import '../../../../data/utils/validators.dart'; +import '../../../layouts/responsive_layout.dart'; +import '../../../providers/manager_provider.dart'; +import '../../../providers/userRoleProvider.dart'; +import '../../../themes/indicators/input_field_decoration.dart'; +import '../../../themes/indicators/text_field_theme.dart'; +import '../../../themes/indicators/text_field_theme_inline_editor.dart' + hide UpperCaseTextFormatter; + +class SubRowWidget extends ConsumerStatefulWidget { + final String selectId; + final int rowId; + + final Function(String) onSave; + final VoidCallback onClose; + + // dropdown values & controllers + final Map controllers; + final List> filteredStaffDataEnqAsgn; + final List> filteredAgentData; + + final String? selectedStaff; + final String? selectedAgent; + final String? selectedInsurer; + final String? selectedBroker; + + final Function(String?)? onStaffChanged; + final Function(String?)? onAgentChanged; + final Function(String?)? onInsurerChanged; + final Function(String?)? onBrokerChanged; + + const SubRowWidget({ + super.key, + required this.selectId, + required this.rowId, + required this.onSave, + required this.onClose, + required this.controllers, + required this.filteredStaffDataEnqAsgn, + required this.filteredAgentData, + required this.selectedStaff, + required this.selectedAgent, + required this.selectedInsurer, + required this.selectedBroker, + this.onStaffChanged, + this.onAgentChanged, + this.onInsurerChanged, + this.onBrokerChanged, + }); + + @override + ConsumerState createState() => _SubRowWidgetState(); +} + +class _SubRowWidgetState extends ConsumerState { + late ApiService apiService; + dynamic userId; + dynamic idPrimary; + dynamic managerId; + dynamic dashboardKey; + dynamic quickQuotationKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + String? selectedFileNames; + String? lastPickedFile; + + dynamic handlerId; + String? _token; + final _formKey = GlobalKey(); + Map fieldErrors = {}; + + List> getStaffData = []; + List> originalData = []; + List> filteredData = []; + bool isLoading = false; + bool isLoadingInsurers = false; + bool isLoadingInsuranceType = false; + bool isLoadingBroker = false; + bool isLoadingPaymentMode = false; + bool isLoadingStaffEnqAssign = false; + bool isLoadingStaffList = false; + bool isLoadingVehicleType = false; + bool isLoadingAgentList = false; + dynamic roleId; + bool _showFilterRow = false; + + Map rowExpanded = {}; + Map controllers = {}; + + String formRebuildKey = UniqueKey().toString(); + + 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 = []; + + 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 = []; + + List> getBrokerData = []; + List> filteredBrokerData = []; + // String? selectedBroker; + + int selectedSeconds = 30; + bool isActionable = false; + + //---------------------- Assign Staff Ends -------------------------------- // + //---------------------- Enquiry TAB Initializations Ends -------------------------------- // + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + + // // ▼ TAKE EDIT VALUES FROM PARENT + // selectedAgent = widget.selectedAgent; + // selectedStaff = widget.selectedStaff; + // selectedInsurer = widget.selectedInsurer; + // selectedBroker = widget.selectedBroker; + + // ▼ USE PARENT CONTROLLERS DIRECTLY (do not recreate) + controllers = widget.controllers.map( + (key, value) => MapEntry(key, value as TextEditingController), + ); + + _initializeToken(); + loadPreferences(); + 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'); + quickQuotationKey = prefs.getString('QuoteKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + print('quickQuotationKey'); + + if (managerId != null) { + print('managerId - $managerId'); + getAgentList(managerId); + } + + final userID = ref.watch(userIdProvider); + + print("managerId - $managerId"); + if (userID != null) { + print('hansles'); + getStaffDetailsForEnquiryAssignment(userID); + } + }); + + getInsurers(); + getBroker(); + } + + Future loadPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + setState(() { + isActionable = prefs.getBool('isActionable') ?? false; + selectedSeconds = prefs.getInt('selectedSeconds') ?? 30; + + _showFilterRow = true; + }); + } + + Future resetPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + await prefs.remove('isActionable'); + await prefs.remove('selectedSeconds'); + + setState(() { + // isActionable = false; // default + selectedSeconds = 30; // default + }); + + print("Storage cleared & reset completed"); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + Future getAgentList(id) async { + print('getAgentListData called'); + setState(() { + isLoadingAgentList = 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(() { + isLoadingAgentList = false; + }); + } + } + + Future getInsurers() async { + print('Insurers called'); + setState(() { + isLoadingInsurers = 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(() { + isLoadingInsurers = false; + }); + } + } + + Future getBroker() async { + print('getBroker called'); + setState(() { + isLoadingBroker = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Broker'); + + if (response['status'] == 200) { + print('getBroker - ${response['data']}'); + setState(() { + getBrokerData = List>.from(response['data']); + print('API Data - $getBrokerData'); + + filteredBrokerData = List.from(getBrokerData); + print('originalData - $filteredBrokerData'); + }); + } else { + getBrokerData = []; + filteredBrokerData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoadingBroker = false; + }); + } + } + + Future getStaffDetailsForEnquiryAssignment(int id) async { + print('getStaffDetailsForEnquiryAssignment called By handler'); + setState(() { + isLoadingStaffEnqAssign = 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(() { + isLoadingStaffEnqAssign = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 12, + runSpacing: 12, + children: [ + buildSelectStaffMem(context), + buildAgentName(context, fromHeader: false), + buildInsurer(context), + buildBroker(context), + buildInsuredName(context), + buildVehicleNumber(context), + buildEmail(context), + buildPhNumber(context), + buildRemarks(context), + + 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, // + onPressed: () => widget.onSave(widget.selectId), + ), + + IconButton( + tooltip: 'Close', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + icon: const Icon( + Icons.close, + color: Colors.indigo, + size: 28, // smaller icon + ), + splashRadius: 14, // + onPressed: widget.onClose, + ), + ], + ); + } + + Widget buildAgentName(BuildContext context, {fromHeader = true}) { + Map? selectedAgntName = filteredAgentData.firstWhere( + (item) => item['id'].toString() == widget.selectedAgent, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Partner', style: _subLabelTimeStyle), + SizedBox(height: 5), + SizedBox( + width: MediaQuery.of(context).size.width * 0.08, + + height: 30, + child: DropdownSearch>( + key: dropDownKeyAgent, + selectedItem: selectedAgntName.isNotEmpty ? selectedAgntName : null, + + 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']}"); + widget.onAgentChanged?.call(val['id']); + // widget.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: ThemedFormField( + 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- ]')), + ], + onChanged: (val) async { + print('Vehicle - $val'); + final res = await apiService.CheckDuplicate(context, val); + print('Vehicle res- $res'); + + if (res["message"] != null && res["message"] != "") { + ToastHelper.showErrorToast(context, res['message']); + setState(() { + fieldErrors['regNo'] = res['message']; // store API message + }); // 🔥 store backend message + } else { + setState(() { + fieldErrors['regNo'] = res['message']; // store API message + }); + } + }, + validator: (value) { + // Show backend validation message + if (fieldErrors['regNo'] != null && + fieldErrors['regNo']!.isNotEmpty) { + return fieldErrors['regNo']; // show API message + } + + return Validators.requiredField(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: widget.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 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: widget.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: widget.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: widget.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 buildSelectStaffMem(ctx) { + Map? selectedVehicle; + try { + selectedVehicle = filteredStaffDataEnqAsgn.firstWhere( + (item) => item['id'].toString() == widget.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) 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: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + ), + Text( + "(${item['today_assigned']} / ${item['total_pending']})", + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.blueGrey.shade200, + ), + ), + ], + ), + ); + }, + ), + + onChanged: (val) { + if (val != null) { + print("Selected Staff : ${val['name']}"); + print("Id: ${val['id']}"); + // widget.selectedStaffName = val['name']; + // widget.selectedStaff = val['id']; + widget.onStaffChanged?.call(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() == widget.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['short_name'].toString().toLowerCase().contains( + filter.toLowerCase(), + ); + }).toList(); + }, + + itemAsString: (val) => val['short_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['short_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['short_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']; + widget.onInsurerChanged?.call(val['id']); + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + Widget buildBroker(BuildContext context) { + Map? selectedBrokers = filteredBrokerData.firstWhere( + (item) => item['id'].toString() == widget.selectedBroker, + orElse: () => {}, + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Broker', style: _subLabelTimeStyle), + SizedBox(height: 5), + 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: Color(0XFF6366F1), + ), + 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: Color(0XFF6366F1), + width: 0.1, + ), + ), + 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: "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']; + + widget.onBrokerChanged?.call(val['id']); + // controllers['agentId']?.text = val['agent_code']; + // agentId = agent['id']; + } + }, + ), + ), + ], + ); + } + + final _subLabelTimeStyle = GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Color(0XFF6366F1), + ); +} diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart index 8c6d986..6e70cef 100644 --- a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart @@ -37,12 +37,10 @@ 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 'enquiry_inline_SubRow.dart'; import 'multi_file_list.dart'; class EnquiryListStaffInline extends ConsumerStatefulWidget { @@ -54,6 +52,12 @@ class EnquiryListStaffInline extends ConsumerStatefulWidget { class EnquiryStaffState extends ConsumerState { int currentPage = 1; int itemsPerPage = 10; + + int completedCurrentPage = 1; + int completedItemsPerPage = 50; + int totalCompletedPages = 1; + List itemsPerPageOptions = [25, 50, 100, 200]; + late ApiService apiService; dynamic userId; dynamic idPrimary; @@ -79,6 +83,10 @@ class EnquiryStaffState extends ConsumerState { List> getStaffData = []; List> originalData = []; List> filteredData = []; + + List> inProgressData = []; + List> completedData = []; + bool isLoading = false; bool isLoadingInsurers = false; bool isLoadingInsuranceType = false; @@ -154,6 +162,7 @@ class EnquiryStaffState extends ConsumerState { int autoRefreshIndex = 0; final List enquiryTabs = ["In Progress", "Completed"]; + int get fullCompletedCount => completedData.length; Map dataDetails() { final data = { @@ -188,10 +197,6 @@ class EnquiryStaffState extends ConsumerState { final GlobalKey>> dropDownKey = GlobalKey>>(); - final GlobalKey>> - dropDownKeyInsurer = GlobalKey>>(); - final GlobalKey>> dropDownKeyAgent = - GlobalKey>>(); final GlobalKey>> dropDownSelectPaymentModeKey = @@ -204,9 +209,6 @@ class EnquiryStaffState extends ConsumerState { final GlobalKey>> dropDownKeyBroker = GlobalKey>>(); - final GlobalKey>> - dropDownSelectStaffKey = - GlobalKey>>(); List> getStaffDetailsDataEnqAsgn = []; List> filteredStaffDataEnqAsgn = []; @@ -279,18 +281,6 @@ class EnquiryStaffState extends ConsumerState { 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); - } ref.listen(enquiryRefreshProvider, (prev, next) { print('quickQuotationKey1'); @@ -302,10 +292,6 @@ class EnquiryStaffState extends ConsumerState { }); }); - getVehicleType(); - getInsurers(); - getInsuranceType(); - getBroker(); getPaymentMode(); refreshSub = ref.listenManual(enquiryRefreshProvider, ( @@ -319,6 +305,7 @@ class EnquiryStaffState extends ConsumerState { ref.read(enquiryRefreshProvider.notifier).state = false; } }); + getInsuranceType(); } Future loadPreferences() async { @@ -400,6 +387,24 @@ class EnquiryStaffState extends ConsumerState { super.dispose(); } + // Add this getter method to calculate paginated completed data + List> get paginatedCompletedData { + if (completedData.isEmpty) return []; + + // Calculate total pages + totalCompletedPages = (completedData.length / completedItemsPerPage).ceil(); + + // Calculate start and end indices + final startIndex = (completedCurrentPage - 1) * completedItemsPerPage; + final endIndex = (startIndex + completedItemsPerPage).clamp( + 0, + completedData.length, + ); + + // Return paginated subset + return completedData.sublist(startIndex, endIndex); + } + Future autoRefrshfilterDateRange() async { print('autorefrshfilterDateRange'); // setState(() async { @@ -454,101 +459,6 @@ class EnquiryStaffState extends ConsumerState { print('refrshfilterDateRange 2'); } - Future getInsurers() async { - print('Insurers called'); - setState(() { - isLoadingInsurers = 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(() { - isLoadingInsurers = false; - }); - } - } - - Future getInsuranceType() async { - print('getClaimList called'); - setState(() { - isLoadingInsuranceType = 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(() { - isLoadingInsuranceType = false; - }); - } - } - - Future getBroker() async { - print('getBroker called'); - setState(() { - isLoadingBroker = true; - }); - - try { - final response = await apiService.fetchMasterDropDown('Broker'); - - if (response['status'] == 200) { - print('getBroker - ${response['data']}'); - setState(() { - getBrokerData = List>.from(response['data']); - print('API Data - $getBrokerData'); - - filteredBrokerData = List.from(getBrokerData); - print('originalData - $filteredBrokerData'); - }); - } else { - getBrokerData = []; - filteredBrokerData = []; - } - } catch (e) { - print('Exception occurred: $e'); - } finally { - setState(() { - isLoadingBroker = false; - }); - } - } - Future getPaymentMode() async { print('getPaymentMode called'); setState(() { @@ -582,40 +492,35 @@ class EnquiryStaffState extends ConsumerState { } } - Future getStaffDetailsForEnquiryAssignment(int id) async { - print('getStaffDetailsForEnquiryAssignment called By handler'); + Future getInsuranceType() async { + print('getClaimList called'); setState(() { - isLoadingStaffEnqAssign = true; + isLoadingInsuranceType = true; }); try { - // final response = await apiService.fetchStaffUserList(id, role); - final response = await apiService.fetchStaffListForEnquiryAssignDropDown( - managerId, - id, - roleId, - ); + final response = await apiService.fetchMasterDropDown('InsuranceType'); - if (response['status'] == 'success') { - print('getStaffDetailsForEnquiryAssignment - ${response['data']}'); + if (response['status'] == 200) { + print('getInsuranceTypeData - ${response['data']}'); setState(() { - getStaffDetailsDataEnqAsgn = List>.from( + getInsuranceTypeData = List>.from( response['data'], ); - print('API Data - $getStaffDetailsDataEnqAsgn'); + print('API Data - $getInsuranceTypeData'); - filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn); - print('originalData - $filteredStaffDataEnqAsgn'); + filteredInsuranceData = List.from(getInsuranceTypeData); + print('originalData - $filteredInsuranceData'); }); } else { - getStaffDetailsDataEnqAsgn = []; - filteredStaffDataEnqAsgn = []; + getInsuranceTypeData = []; + filteredInsuranceData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { - isLoadingStaffEnqAssign = false; + isLoadingInsuranceType = false; }); } } @@ -686,25 +591,6 @@ class EnquiryStaffState extends ConsumerState { String finalStatus = SelectedStatus; print('finalStatus1 - $finalStatus'); - // if ((finalStatus != '') && - // (SelectedStatus != 'Completed') && - // (autoRefreshIndex == 0)) { - // finalStatus = 'OtherThenCompleted'; - // print('finalSt1 - $finalStatus'); - // } else if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) { - // finalStatus = 'Completed'; - // print('finalSt2 - $finalStatus'); - // } else if (SelectedStatus == 'PrevPending') { - // finalStatus = ''; - // print('finalSt3 - $finalStatus'); - // } else { - // finalStatus = 'OtherThenCompleted'; - // print('finalSt4 - $finalStatus'); - // // finalStatus = SelectedStatus; // keep original - // } - // print('finalStatus2 - $finalStatus'); - // print('DAJJshA79SelectedStatus - $SelectedStatus'); - try { final response = await apiService.fetchEnquiryList( managerId, @@ -731,17 +617,29 @@ class EnquiryStaffState extends ConsumerState { print('ToDate : $toDate'); setState(() { - // if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) { - // selectedIndex = 1; - // SelectedStatus = 'Completed'; - // } else { - // selectedIndex = 0; - // SelectedStatus = 'OtherThenCompleted'; - // } controllers['startDate']?.text = fromDate; controllers['endDate']?.text = toDate; if (data is List) { getStaffData = List>.from(data); + + originalData = List>.from(data); + + // Split data immediately after fetching + inProgressData = originalData + .where((item) => item['enquiry_status'] != 'Completed') + .toList(); + + print('inProgressData - $inProgressData'); + + completedData = originalData + .where((item) => item['enquiry_status'] == 'Completed') + .toList(); + print('completedData - $completedData'); + + // filteredData now holds the currently displayed tab's data (without search) + filteredData = (selectedIndex == 0) + ? inProgressData + : completedData; } else if (data is Map) { getStaffData = [Map.from(data)]; } else { @@ -763,27 +661,12 @@ class EnquiryStaffState extends ConsumerState { } } - 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 sourceList = (selectedIndex == 0) ? inProgressData : completedData; + + filteredData = sourceList.where((item) { // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; return (item['agent_name'] ?? '-').toLowerCase().contains( @@ -974,6 +857,8 @@ class EnquiryStaffState extends ConsumerState { idPrimary = id; handleEnqTabSave(); + + setState(() => rowExpanded.clear()); } void handleCancel(int id) { @@ -994,11 +879,7 @@ class EnquiryStaffState extends ConsumerState { ); // 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'] == '-') - ) { + if (row.isNotEmpty) { getStaffData.removeWhere( (item) => item['id'].toString() == id.toString(), ); @@ -1014,516 +895,9 @@ class EnquiryStaffState extends ConsumerState { // 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: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item['name'].toString(), - style: GoogleFonts.inter( - fontSize: 12, - color: Colors.black, - ), - ), - Text( - "(${item['today_assigned']} / ${item['total_pending']})", - style: GoogleFonts.poppins( - fontSize: 10, - color: Colors.blueGrey.shade200, - ), - ), - ], - ), - ); - }, - ), - - 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['short_name'].toString().toLowerCase().contains( - filter.toLowerCase(), - ); - }).toList(); - }, - - itemAsString: (val) => val['short_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['short_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['short_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 Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Broker', style: _subLabelTimeStyle), - SizedBox(height: 5), - 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: Color(0XFF6366F1), - ), - 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: Color(0XFF6366F1), - width: 0.1, - ), - ), - 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: "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() { @@ -1550,965 +924,6 @@ class EnquiryStaffState extends ConsumerState { return data; } - Future getVehicleType() async { - print('getClaimList called'); - setState(() { - isLoadingVehicleType = true; - }); - - try { - final response = await apiService.fetchMasterDropDown('vehicleType'); - - 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(() { - isLoadingVehicleType = false; - }); - } - } - - Future getAgentList(id) async { - print('getAgentListData called'); - setState(() { - isLoadingAgentList = 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(() { - isLoadingAgentList = 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: ThemedFormField( - 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- ]')), - ], - onChanged: (val) async { - print('Vehicle - $val'); - final res = await apiService.CheckDuplicate(context, val); - print('Vehicle res- $res'); - - if (res["message"] != null && res["message"] != "") { - ToastHelper.showErrorToast(context, res['message']); - setState(() { - fieldErrors['regNo'] = res['message']; // store API message - }); // 🔥 store backend message - } else { - setState(() { - fieldErrors['regNo'] = res['message']; // store API message - }); - } - }, - validator: (value) { - // Show backend validation message - if (fieldErrors['regNo'] != null && - fieldErrors['regNo']!.isNotEmpty) { - return fieldErrors['regNo']; // show API message - } - - return Validators.requiredField(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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', - apiUrl: - 'api/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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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(() { @@ -2643,312 +1058,6 @@ class EnquiryStaffState extends ConsumerState { ); } - 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), - buildBroker(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), - }, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: IconButton( - tooltip: 'Close', - padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: 24, minHeight: 24), - icon: const Icon( - Icons.close, - color: Colors.indigo, - size: 28, // smaller icon - ), - splashRadius: 14, // ripple radius - onPressed: () { - setState(() { - // isActionable = false; - bool isOpen = rowExpanded[id] ?? false; - - rowExpanded.clear(); // close all - - // rowExpanded[id] = !(rowExpanded[id] ?? false); - }); - }, - ), - ), - ], - ); - } - - 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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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 = - // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; - final path = - 'api/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 && @@ -3103,7 +1212,7 @@ class EnquiryStaffState extends ConsumerState { print("⚠️ No file selected!"); } - // 3️⃣ Submit request + // Submit request try { final response = await http.Response.fromStream(await request.send()); @@ -3137,21 +1246,6 @@ class EnquiryStaffState extends ConsumerState { 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), @@ -3217,31 +1311,6 @@ class EnquiryStaffState extends ConsumerState { // ), 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; - // }); - // }, - // ), - // ), ], ), ), @@ -3294,9 +1363,6 @@ class EnquiryStaffState extends ConsumerState { print("Dialog closed"); print("Dialog result: $result"); if (result) { - // setState(() { - // isActionable = false; - // }); refrshfilterDateRange(); } } @@ -3419,10 +1485,6 @@ class EnquiryStaffState extends ConsumerState { color: Color(0xFFE2E8F0), width: 2, ), - // right: BorderSide( - // color: Color(0xFFE2E8F0), - // width: 2, - // ), ), ), height: 30, @@ -3450,29 +1512,11 @@ class EnquiryStaffState extends ConsumerState { ), ), ), - // - // InkWell( - // onTap: () { - // resetPreferences(); - // }, - // child: Icon(Icons.refresh, size: 18), - // ), ], ), ), SizedBox(width: 10), MouseRegion( - onEnter: (_) { - // setState(() { - // isActionable = true; - // }); - }, - onExit: (_) { - // setState(() { - // // isActionable = false; - // isActionable = _searchStaffController.text.isNotEmpty; - // }); - }, child: ThemedSearchField( hintText: 'Search', backgroundColor: Color(0xFFFFFFFF), @@ -3527,42 +1571,25 @@ class EnquiryStaffState extends ConsumerState { SizedBox(child: _buildStyledTabs()), ResponsiveLayout.isMobile(context) ? _buildDataTable(context) - : - // Expanded( - // child: - Container( + : Container( height: MediaQuery.of(context).size.height * 0.55, - decoration: BoxDecoration(color: Colors.white), + // 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, 'To be assigned': true, 'Assigned': true, 'In progress': 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', - // 'Policy Created': 'Policy Created', 'To be assigned': 'To Be Assigned', 'Assigned': 'Assigned', 'In progress': 'In Progress', @@ -3571,9 +1598,12 @@ class EnquiryStaffState extends ConsumerState { List> get filteredDataByTab { if (selectedIndex == 0) { // Progress enquiries - final sortedData = [...filteredData] + final sortedData = [...inProgressData] ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + // final sortedData = [...filteredData] + // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + if (sortedData.isEmpty) return []; return sortedData.where((e) { return [ @@ -3583,14 +1613,18 @@ class EnquiryStaffState extends ConsumerState { ].contains(e['enquiry_status']); }).toList(); } else { - final sortedData = [...filteredData] + // final sortedData = [...filteredData] + // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + final sortedData = [...completedData] ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); if (sortedData.isEmpty) return []; // Completed enquiries - return sortedData.where((e) { - return e['enquiry_status'] == "Completed"; - }).toList(); + return paginatedCompletedData; + // return sortedData.where((e) { + // return e['enquiry_status'] == "Completed"; + // }).toList(); } } @@ -3616,23 +1650,6 @@ class EnquiryStaffState extends ConsumerState { 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 @@ -3660,17 +1677,8 @@ class EnquiryStaffState extends ConsumerState { // '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( @@ -3733,9 +1741,6 @@ class EnquiryStaffState extends ConsumerState { // child: LayoutBuilder( builder: (context, constraints) { - // double minWidth = constraints.maxWidth < 1300 - // ? 1300 - // : constraints.maxWidth; double tableWidth = constraints.maxWidth > minTableWidth ? constraints.maxWidth : minTableWidth; @@ -3761,14 +1766,6 @@ class EnquiryStaffState extends ConsumerState { // 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), ), @@ -3776,21 +1773,11 @@ class EnquiryStaffState extends ConsumerState { // 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), - // ), - // ], - ), + decoration: BoxDecoration(color: Colors.white), child: _buildDataTableContent(), // 👈 New method ), ), + _buildPaginationControls(), ], ), ), @@ -3801,6 +1788,8 @@ class EnquiryStaffState extends ConsumerState { // ); } + // Replace your _buildDataTableContent() method with this optimized version + Widget _buildDataTableContent() { if (filteredData.isEmpty) { return const SizedBox( @@ -3811,1373 +1800,128 @@ class EnquiryStaffState extends ConsumerState { 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; + // Flatten the grouped data into a single list with group headers + final List flattenedList = []; - return Column( + groupedData.entries.forEach((entry) { + final status = entry.key; + final items = entry.value; + + if (items.isNotEmpty) { + // Add group header + flattenedList.add({ + 'type': 'header', + 'status': status, + // 'count': items.length, + 'count': selectedIndex == 1 + ? fullCompletedCount // <-- Show full completed count + : items.length, // For progress, show filtered count + 'isExpanded': groupExpanded[status] ?? true, + }); + + // Add items if expanded + if (groupExpanded[status] ?? true) { + flattenedList.addAll( + items.map((item) => {'type': 'item', 'data': item}), + ); + } + } + }); + + return ListView.builder( + itemCount: flattenedList.length, + itemBuilder: (context, index) { + final item = flattenedList[index]; + + if (item['type'] == 'header') { + // Render group header + return Container( + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.fromLTRB(12, 4, 12, 4), + child: _buildGroupHeader(item['status'], item['count']), + ); + } else { + // Render data row + final data = item['data']; + final sno = index; // You can calculate proper serial number if needed + final id = int.tryParse(data['id'].toString()) ?? 0; + final selectId = data['id'].toString() ?? '0'; + + return Container( + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.fromLTRB(12, 8, 12, 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color(0xFFFFECF1F7)), + borderRadius: BorderRadius.circular(8), + ), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // Group Header - // if (status != 'Proposal Accepted' && - // status != 'Proposal Rejected') ...[ - if (items.isNotEmpty) ...[ + DataTable( + border: TableBorder.all(color: Colors.transparent, width: 0), + headingRowHeight: 0, + dividerThickness: 0.1, + columnSpacing: 20.0, + dataRowMinHeight: 40, + columns: _buildDataColumns(), + rows: [_buildDataRow(data, sno)], + ), + + // Expanded content + if (rowExpanded[id] ?? false) 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) - // - // 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'] ?? '-'; - String status = item['enquiry_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( - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 60, - child: Text( - item['insurer_short_name'] != null - ? item['insurer_short_name'] - : '-', - - style: _dataBoldthm2, - ), - ), - Tooltip( - message: 'Broker : ${item['broker_name']}', - child: Text( - item['broker_name'] != null - ? _truncateText(item['broker_name'], 8) - : '-', - - style: _dataBoldthm4, - ), - ), - ], - ), - ), - - 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'] ?? '-', - textAlign: TextAlign.center, - style: _dataBoldthm3, - ), - ), - // ), - // child: Text(item['reg_no'] ?? '-', style: _dataBold), - // ), - ), - ), - ), - ), - - DataCell( - SizedBox( - width: 80.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"]), - ], - ), + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFC7D2FE)), + color: Color(0xFFF8F9FF), ), - ), - 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, - ); + padding: const EdgeInsets.symmetric( + vertical: 14.0, + horizontal: 8.0, + ), + child: SubRowWidget( + selectId: selectId, + rowId: id, + onSave: handleSave, + onClose: () { + setState(() => rowExpanded.clear()); }, - 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, - ), - ), - ), - ), - ), - - if (item['enquiry_status'] != 'Completed' && - roleId == 'manager') ...[ - IconButton( - padding: EdgeInsets.zero, // remove outer padding - constraints: BoxConstraints( - // reduce clickable area - minWidth: 24, - minHeight: 24, - ), - icon: Icon( - Icons.delete, - color: Colors.red.shade400, - size: 15, - ), - - tooltip: "Delete Enquiry", - onPressed: () { - setState(() {}); - handleDeleteEnq(item); - }, - ), - ], - ], - ), - ), - ), - - DataCell( - SizedBox( - width: 120, - - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (item['enquiry_status'] != 'Assigned') ...[ - Material( - color: Colors.white, - child: InkWell( - // onTap: null, - hoverColor: Color(0xFFEAF6F4), - onTap: (item['enquiry_status'] == 'Completed') - ? () async { - final prefs = - await SharedPreferences.getInstance(); - - // ✅ Remove old value (if any) - await prefs.remove('enqStaffDataId'); - - // ✅ Save the new id - await prefs.setString( - 'enqStaffDataId', - id.toString(), - ); - - // Read it back if needed - final dynamic? enqStaffDataId = prefs.getString( - 'enqStaffDataId', - ); - - // ✅ Update provider too - ref - .read(quotationStaffIdProvider.notifier) - .state = - enqStaffDataId; - - buildPolicyCreatedStatusActions( - context, - status, - item['id'], - ); - } - : null, - 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['enquiry_status'] ?? '-', - // 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), - ), - ), - ], - if ((item['enquiry_status'] == 'Assigned') && - (item['quotation_id'] != null)) ...[ - TextButton( - onPressed: () async { - final res = await apiService.updateEnquiryInProgress(id); - if (res['status'] == 'success') { - refrshfilterDateRange(); - } - print('updateEnquiryInProgress - $res'); - }, - - child: Tooltip( - message: 'Click to Proceed', - waitDuration: const Duration( - milliseconds: 500, - ), // optional - showDuration: const Duration(seconds: 2), - child: Text( - 'PROCEED', - style: GoogleFonts.poppins( - fontSize: 11, - fontWeight: FontWeight.w500, - color: Colors.green, - ), - ), - ), - ), - ], - - if ((item['enquiry_status'] == 'Assigned') && - (item['quotation_id'] == null)) ...[ - TextButton( - onPressed: () 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 Create Quote', - waitDuration: const Duration( - milliseconds: 500, - ), // optional - showDuration: const Duration(seconds: 2), - child: Text( - 'Create Quote', - style: GoogleFonts.poppins( - fontSize: 11, - fontWeight: FontWeight.w500, - color: Colors.blue, - ), - ), - ), - ), - ], - ], - ), - ), - ), - // 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['enquiry_status'] == 'To be assigned') || - ((item['enquiry_status'] == 'Assigned') && - (item['quotation_id'] == null))) - ? 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['enquiry_status'] == 'To be assigned') || - ((item['enquiry_status'] == 'Assigned') && - (item['quotation_id'] == null))) - ? 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(); + controllers: controllers, + selectedAgent: selectedAgent, + selectedStaff: selectedStaff, + selectedBroker: selectedBroker, + selectedInsurer: selectedInsurer, + filteredAgentData: filteredAgentData, + filteredStaffDataEnqAsgn: filteredStaffDataEnqAsgn, + onStaffChanged: (val) { + setState(() { + selectedStaff = val; + }); }, - - 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, - ), - ); + onAgentChanged: (val) { + setState(() { + selectedAgent = val; + }); }, - 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']); + onInsurerChanged: (val) { + setState(() { + selectedInsurer = val; + }); + }, + onBrokerChanged: (val) { + setState(() { + selectedBroker = val; + }); }, ), ), - ), - ), - - // DataCell( - // buildPaymentMode(context), - // // Builder( - // // builder: (cellContext) => buildPaymentMode(cellContext), - // // // SizedBox(width: 200, child: buildPaymentMode(cellContext)), - // // ), - // ), - - // Policy Number - DataCell( - SizedBox( - height: 30, - width: 90, - child: - ((item['enquiry_status'] == 'To be assigned') || - ((item['enquiry_status'] == 'Assigned') && - (item['quotation_id'] == null))) - ? Text('-') - : Row( - children: [ - if ((((item['enquiry_status'] == 'Completed') && - (item['quotation_id'] != null)))) ...[ - // Text(item['policy_pdf_file_name']), - Tooltip( - message: 'Download Policy File', - child: InkWell( - onTap: () => apiService.downloadFile( - apiUrl: - 'api/policy/downloadPolicyFile?policy_id=${item["policy_id"]}&file_type=policy_pdf', - apiId: item["policy_id"], - localFile: null, - fileName: item['policy_pdf_file_name'], - ), - - child: Icon( - Icons.file_download, - size: 14, - color: Colors.blueAccent, - ), - ), - ), - ], - - 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( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Upload Policy PDF', - style: _dataBold, - ), - SizedBox(height: 10), - ThemedUploadField( - // key: ValueKey(selectedFileNames), - // 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 (lastPickedFile == fileName) { - print( - "❌ Cancel clicked. Ignoring old file.", - ); - ToastHelper.showErrorToast( - context, - 'Please upload a new file. No previous file exists.', - ); - setState(() { - print('Clear Fields'); - selectedFileNames = - null; // clears the field - docUploadedFile = null; - print('Clear Fields - 1'); - }); - // Navigator.of(context).pop(); - // refrshfilterDateRange(); - return; - } - - lastPickedFile = fileName; - - // setState(() { - // docUploadedFile = null; - // selectedFileNames = null; - // }); - - // ❗ User canceled picker - if (file == null || - fileName == null || - fileName.isEmpty || - file.size == 0) { - print( - "❌ File selection canceled. No upload.", - ); - return; - } - - 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["policy_id"], // ONLY while updating - }, - ); - Navigator.of(context).pop(); - // if (selectedBrokerName == 'Nhance') { - // handleUploadPolicy(); - // } - }, - ), - ], - ), - ), - ], - color: Colors.white, - ); - }, - child: Tooltip( - message: - 'Click and Upload Policy PDF', // 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, - ), - ), - ), - ), - ), - ], - ), - ), - ), - - // 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), - // ), - // ), - // ), - // ), - ], + ], + ), + ); + } + }, ); } @@ -5202,7 +1946,6 @@ class EnquiryStaffState extends ConsumerState { ]; } - // 6. Build table header Widget _buildTableHeader(bool isDesktop) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -5247,6 +1990,729 @@ class EnquiryStaffState extends ConsumerState { ); } + // Optimized _buildDataRow - Extract reusable widgets and reduce nesting + + // 1. First, add these helper widget methods to your class: + + // Helper: Build action buttons + Widget _buildActionButtons( + Map item, + int id, + String selectId, + ) { + return Row( + children: [ + _buildInfoTooltip(item), + _buildEditButton(item, id), + _buildFileUploadButton(selectId), + if (item['enquiry_status'] != 'Completed' && roleId == 'manager') + _buildDeleteButton(item), + ], + ); + } + + // Helper: Info tooltip + Widget _buildInfoTooltip(Map item) { + return Tooltip( + richMessage: WidgetSpan( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Color(0xFFFFFCF2), + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _row("Email", item["email"] != null ? item["email"] : ''), + _row("Mobile", item["mobile"] != null ? item["mobile"] : ''), + _row("Remarks", item["remarks"] != null ? item["remarks"] : ''), + ], + ), + ), + ), + padding: EdgeInsets.zero, + verticalOffset: 12, + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + decoration: BoxDecoration(), + child: Icon(Icons.info_outline_rounded, size: 15, color: Colors.blue), + ); + } + + // Helper: Edit button + Widget _buildEditButton(Map item, int id) { + return IconButton( + padding: EdgeInsets.zero, + constraints: BoxConstraints(minWidth: 24, minHeight: 24), + icon: Icon( + rowExpanded[id] == true ? Icons.edit_off_sharp : Icons.edit, + color: Colors.blue, + size: 15, + ), + tooltip: "Edit", + onPressed: () { + setState(() { + bool isOpen = rowExpanded[id] ?? false; + rowExpanded.clear(); + if (!isOpen) rowExpanded[id] = true; + }); + handleEdit(item); + }, + ); + } + + // Helper: File upload button with menu + Widget _buildFileUploadButton(String selectId) { + return Builder( + builder: (buttonContext) => Material( + color: Colors.white, + child: InkWell( + onTap: () async { + final button = buttonContext.findRenderObject() as RenderBox; + final overlay = + Overlay.of(buttonContext).context.findRenderObject() + as RenderBox; + final 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: MultipleFileWidget(enqId: selectId)), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: 'Click and Upload / Download Enquiry Files', + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Icon( + Icons.file_upload_outlined, + size: 15, + color: Colors.green, + ), + ), + ), + ), + ); + } + + // Helper: Delete button + Widget _buildDeleteButton(Map item) { + return IconButton( + padding: EdgeInsets.zero, + constraints: BoxConstraints(minWidth: 24, minHeight: 24), + icon: Icon(Icons.delete, color: Colors.red.shade400, size: 15), + tooltip: "Delete Enquiry", + onPressed: () => handleDeleteEnq(item), + ); + } + + // Helper: Status cell with conditional actions + Widget _buildStatusCell( + Map item, + int id, + Color bgColor, + Color borderColor, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item['enquiry_status'] != 'Assigned') + _buildStatusContainer(item, id, bgColor, borderColor), + if (item['enquiry_status'] == 'Assigned' && + item['quotation_id'] != null) + _buildProceedButton(id), + if (item['enquiry_status'] == 'Assigned' && + item['quotation_id'] == null) + _buildCreateQuoteButton(item), + ], + ); + } + + // Helper: Status display container + Widget _buildStatusContainer( + Map item, + int id, + Color bgColor, + Color borderColor, + ) { + return Material( + color: Colors.white, + child: InkWell( + hoverColor: Color(0xFFEAF6F4), + onTap: null, + + child: Container( + padding: EdgeInsets.symmetric(vertical: 2.0), + margin: EdgeInsets.symmetric(vertical: 2), + decoration: BoxDecoration( + color: bgColor, + border: Border.all(color: borderColor), + borderRadius: BorderRadius.circular(5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + item['enquiry_status'] ?? '-', + style: GoogleFonts.inter( + fontSize: 10, + color: Color(0XFF1e293b), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ); + } + + // Helper: Proceed button + Widget _buildProceedButton(int id) { + return TextButton( + onPressed: () async { + final res = await apiService.updateEnquiryInProgress(id); + if (res['status'] == 'success') refrshfilterDateRange(); + }, + child: Tooltip( + message: 'Click to Proceed', + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Text( + 'PROCEED', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.green, + ), + ), + ), + ); + } + + // Helper: Create quote button + Widget _buildCreateQuoteButton(Map item) { + return TextButton( + onPressed: () async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('enqStaffDataId'); + await prefs.setString('enqStaffDataId', item['id'].toString()); + ref.read(quotationStaffIdProvider.notifier).state = item['id']; + buildStatusActions(context, item['status'], item['id']); + }, + child: Tooltip( + message: 'Click to Create Quote', + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Text( + 'Create Quote', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.blue, + ), + ), + ), + ); + } + + // Helper: Payment mode dropdown + Widget _buildPaymentModeDropdown(Map item) { + if (item['enquiry_status'] == 'To be assigned' || + (item['enquiry_status'] == 'Assigned' && + item['quotation_id'] == null)) { + return Text('-'); + } + + return DropdownSearch>( + key: ValueKey('payment_${item['id']}'), + selectedItem: _getSelectedPaymentMode(item), + items: (filter, _) async => _filterPaymentModes(filter), + itemAsString: (val) => val['value'].toString(), + compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], + suffixProps: _buildDropdownSuffix(), + dropdownBuilder: (context, selectedItem) => + _buildDropdownDisplay(selectedItem, 'value', 'Select Payment Mode'), + decoratorProps: _buildDropdownDecorator(), + popupProps: _buildDropdownPopup('value'), + onChanged: (val) { + if (val != null) { + setState(() { + item['payment_mode_id'] = val['id'].toString(); + item['payment_mode'] = val['value'].toString(); + }); + handlePaymentMode(val['id'].toString(), item['quotation_id']); + } + }, + ); + } + + // Helper: Insurance plan type dropdown + Widget _buildInsurancePlanDropdown(Map item) { + if (item['enquiry_status'] == 'To be assigned' || + (item['enquiry_status'] == 'Assigned' && + item['quotation_id'] == null)) { + return Text('-'); + } + + return DropdownSearch>( + key: ValueKey('insurance_${item['id']}'), + selectedItem: _getSelectedInsurancePlan(item), + items: (filter, _) async => _filterInsurancePlans(filter), + itemAsString: (val) => val['insurance_plan_type'].toString(), + compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], + suffixProps: _buildDropdownSuffix(), + dropdownBuilder: (context, selectedItem) => _buildDropdownDisplay( + selectedItem, + 'insurance_plan_type', + 'Select Insurer', + ), + decoratorProps: _buildDropdownDecorator(), + popupProps: _buildDropdownPopup('insurance_plan_type'), + onChanged: (val) { + if (val != null) { + setState(() { + item['insurance_plan_type'] = val['insurance_plan_type'].toString(); + }); + handleInsurancePlanTypeMode( + val['id'].toString(), + item['quotation_id'], + ); + } + }, + ); + } + + // Helper: Policy upload/download + Widget _buildPolicyCell(Map item) { + if (item['enquiry_status'] == 'To be assigned' || + (item['enquiry_status'] == 'Assigned' && + item['quotation_id'] == null)) { + return Text('-'); + } + + return Row( + children: [ + if (item['enquiry_status'] == 'Completed' && + item['quotation_id'] != null) + _buildPolicyDownloadButton(item), + _buildPolicyUploadButton(item), + ], + ); + } + + // Helper: Policy download button + Widget _buildPolicyDownloadButton(Map item) { + return Tooltip( + message: 'Download Policy File', + child: InkWell( + onTap: () => apiService.downloadFile( + apiUrl: + 'api/policy/downloadPolicyFile?policy_id=${item["policy_id"]}&file_type=policy_pdf', + apiId: item["policy_id"], + localFile: null, + fileName: item['policy_pdf_file_name'], + ), + child: Icon(Icons.file_download, size: 14, color: Colors.blueAccent), + ), + ); + } + + // Helper: Policy upload button with menu + Widget _buildPolicyUploadButton(Map item) { + return Builder( + builder: (buttonContext) => Material( + color: Colors.white, + child: InkWell( + onTap: () { + if (item['payment_mode_id'] != null) { + _showPolicyUploadMenu(buttonContext, item); + } else { + ToastHelper.showErrorToast(context, 'Please select payment mode'); + } + }, + child: Tooltip( + message: 'Click and Upload Policy PDF', + waitDuration: Duration(milliseconds: 500), + showDuration: Duration(seconds: 2), + child: Icon( + Icons.file_upload_outlined, + size: 15, + color: Colors.green, + ), + ), + ), + ), + ); + } + + // Helper: Show policy upload menu + Future _showPolicyUploadMenu( + BuildContext buttonContext, + Map item, + ) async { + final button = buttonContext.findRenderObject() as RenderBox; + final overlay = + Overlay.of(buttonContext).context.findRenderObject() as RenderBox; + final 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( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text('Upload Policy PDF', style: _dataBold), + SizedBox(height: 10), + _buildPolicyUploadField(item), + ], + ), + ), + ], + color: Colors.white, + ); + } + + // Helper: Policy upload field + + Widget _buildPolicyUploadField(Map item) { + return ThemedUploadField( + hintText: + selectedFileNames ?? + item['policy_pdf_file_name'] ?? + "Upload Document", + padHorizontal: 4, + padVertical: 5, + fontSZ: 11, + borderCirculr: 5, + txtwidth: ResponsiveLayout.isMobile(context) + ? null + : MediaQuery.of(context).size.width * 0.18, + borderColor: Color(0xFFE2E8F0), + onFileSelected: (fileName, file) => + _handlePolicyFileUpload(fileName, file, item), + ); + } + + // Helper: Handle policy file upload + Future _handlePolicyFileUpload( + String? fileName, + PlatformFile? file, + Map item, + ) async { + if (lastPickedFile == fileName) { + ToastHelper.showErrorToast(context, 'Please upload a new file.'); + setState(() { + selectedFileNames = null; + docUploadedFile = null; + }); + return; + } + + if (file == null || + fileName == null || + fileName.isEmpty || + file.size == 0) { + return; + } + + if (!fileName.toLowerCase().endsWith('.pdf')) { + ToastHelper.showErrorToast(context, 'Only PDF files are allowed.'); + return; + } + + lastPickedFile = fileName; + setState(() => docUploadedFile = file); + + await uploadPolicyPDF( + file: docUploadedFile, + data: { + "quotation_id": item["quotation_id"], + "manager_id": managerId, + "created_by": userId, + "updated_by": userId, + "id": item["policy_id"], + }, + ); + Navigator.of(context).pop(); + } + + // Helper methods for dropdown reusability + Map? _getSelectedPaymentMode(Map item) { + try { + return filteredPaymentModeData.firstWhere( + (mode) => mode['id'].toString() == item['payment_mode_id'].toString(), + orElse: () => {}, + ); + } catch (e) { + return null; + } + } + + Map? _getSelectedInsurancePlan(Map item) { + try { + return filteredInsuranceData.firstWhere( + (mode) => + mode['insurance_plan_type'].toString().trim().toLowerCase() == + item['insurance_plan_type'].toString().trim().toLowerCase(), + orElse: () => {}, + ); + } catch (e) { + return null; + } + } + + List> _filterPaymentModes(String filter) { + if (filter.isEmpty) return filteredPaymentModeData; + return filteredPaymentModeData.where((mode) { + return mode['value'].toString().toLowerCase().contains( + filter.toLowerCase(), + ); + }).toList(); + } + + List> _filterInsurancePlans(String filter) { + if (filter.isEmpty) return filteredInsuranceData; + return filteredInsuranceData.where((plan) { + return plan['insurance_plan_type'].toString().toLowerCase().contains( + filter.toLowerCase(), + ); + }).toList(); + } + + DropdownSuffixProps _buildDropdownSuffix() { + return DropdownSuffixProps( + dropdownButtonProps: DropdownButtonProps( + isVisible: true, + padding: EdgeInsets.zero, + constraints: BoxConstraints(minWidth: 12, minHeight: 12), + iconSize: 8, + color: Colors.black45, + ), + ); + } + + Widget _buildDropdownDisplay(dynamic selectedItem, String key, String hint) { + bool isEmpty = + selectedItem == null || + selectedItem.isEmpty || + selectedItem[key] == null; + + return Container( + padding: EdgeInsets.zero, + alignment: Alignment.centerLeft, + child: Text( + isEmpty ? hint : selectedItem[key].toString(), + style: GoogleFonts.poppins( + fontSize: 11, + color: isEmpty ? Colors.grey.shade500 : Colors.black, + fontWeight: FontWeight.w400, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ); + } + + DropDownDecoratorProps _buildDropdownDecorator() { + return DropDownDecoratorProps( + decoration: AppInputDecorations.dropdownDecoration().copyWith( + filled: true, + fillColor: Colors.white, + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide(color: Color(0xFFE2E8F0), width: 0.5), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide(color: Color(0xFFE2E8F0), width: 0.5), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 1, vertical: 6), + ), + ); + } + + // 2. Now the OPTIMIZED _buildDataRow method: + DataRow _buildDataRow(Map item, int sno) { + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + final status = item['enquiry_status'] ?? '-'; + final bgColor = statusColors[status] ?? Color(0xFFF9FAFB); + final borderColor = statusBorderColors[status] ?? Color(0xFFE2E8F0); + + return DataRow( + cells: [ + // Received Date + DataCell( + SizedBox( + width: 70, + child: Text( + formatDateTimeForTable(item['created_on'], newLine: true), + style: _dataBoldthm1, + ), + ), + ), + + // Assigned To + DataCell( + SizedBox( + 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), + ], + ), + ), + ), + + // Partner + 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 + DataCell( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 60, + child: Text( + item['insurer_short_name'] ?? '-', + style: _dataBoldthm2, + ), + ), + Tooltip( + message: 'Broker : ${item['broker_name']}', + child: Text( + item['broker_name'] != null + ? _truncateText(item['broker_name'], 8) + : '-', + style: _dataBoldthm4, + ), + ), + ], + ), + ), + + // Insured Name + 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: 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), + ), + ), + ), + + // Action Buttons + DataCell( + SizedBox(width: 80.0, child: _buildActionButtons(item, id, selectId)), + ), + + // Status + DataCell( + SizedBox( + width: 120, + child: _buildStatusCell(item, id, bgColor, borderColor), + ), + ), + + // Payment Mode + DataCell( + SizedBox( + height: 30, + width: 120, + child: _buildPaymentModeDropdown(item), + ), + ), + + // Plan Type + DataCell( + SizedBox( + width: 120, + height: 30, + child: _buildInsurancePlanDropdown(item), + ), + ), + + // Policy + DataCell( + SizedBox(height: 30, width: 90, child: _buildPolicyCell(item)), + ), + ], + ); + } + Widget _row(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), @@ -5302,17 +2768,12 @@ class EnquiryStaffState extends ConsumerState { child: GestureDetector( onTap: () { setState(() { + print('TAPPED TAB'); selectedIndex = index; - // if (selectedIndex == '1') { - // getStaffList( - // managerId, - // roleId, - // SelectedStatus: 'Completed', - // ); - // } else { - // getStaffList(managerId, roleId); - // } + if (index == 1) { + completedCurrentPage = 1; + } }); }, child: AnimatedContainer( @@ -5414,7 +2875,6 @@ class EnquiryStaffState extends ConsumerState { color: Colors.black, // color: Color(0XFF94A3B8), ); - final _subLabelTimeStyle = GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w400, @@ -5426,13 +2886,11 @@ class EnquiryStaffState extends ConsumerState { 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), @@ -5443,18 +2901,15 @@ class EnquiryStaffState extends ConsumerState { 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 _formatStatus(String status) { return status .split(' ') @@ -5519,4 +2974,222 @@ class EnquiryStaffState extends ConsumerState { return s.isNotEmpty ? s : '-'; } } + + PopupProps> _buildDropdownPopup(String displayKey) { + return 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[displayKey].toString(), + style: GoogleFonts.inter( + fontSize: 11, + color: Colors.black87, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + ), + ), + ); + }, + ); + } + + // Add pagination controls widget + Widget _buildPaginationControls() { + if (selectedIndex != 1 || completedData.length <= 25) { + return SizedBox.shrink(); + } + + return Container( + padding: EdgeInsets.symmetric(vertical: 2, horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: Color(0xFFE2E8F0))), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Left side: Page info and items per page selector + Row( + children: [ + Text( + 'Showing ${((completedCurrentPage - 1) * completedItemsPerPage) + 1}-${(completedCurrentPage * completedItemsPerPage).clamp(0, completedData.length)} of ${completedData.length}', + style: GoogleFonts.inter( + fontSize: 10, + color: Color(0xFF64748B), + ), + ), + SizedBox(width: 16), + // Items per page dropdown + Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFE2E8F0)), + borderRadius: BorderRadius.circular(6), + ), + child: DropdownButton( + value: completedItemsPerPage, + underline: SizedBox(), + isDense: true, + items: itemsPerPageOptions.map((int value) { + return DropdownMenuItem( + value: value, + child: Text( + '$value / page', + style: GoogleFonts.inter( + fontSize: 10, + color: Color(0xFF334155), + ), + ), + ); + }).toList(), + onChanged: (int? newValue) { + if (newValue != null) { + setState(() { + completedItemsPerPage = newValue; + completedCurrentPage = 1; // Reset to first page + }); + } + }, + ), + ), + ], + ), + + // Right side: Pagination buttons + Row( + children: [ + // First page + IconButton( + icon: Icon(Icons.first_page, size: 18), + onPressed: completedCurrentPage > 1 + ? () => setState(() => completedCurrentPage = 1) + : null, + color: completedCurrentPage > 1 + ? Color(0xFF2E7D6E) + : Colors.grey, + tooltip: 'First page', + ), + + // Previous page + IconButton( + icon: Icon(Icons.chevron_left, size: 18), + onPressed: completedCurrentPage > 1 + ? () => setState(() => completedCurrentPage--) + : null, + color: completedCurrentPage > 1 + ? Color(0xFF2E7D6E) + : Colors.grey, + tooltip: 'Previous page', + ), + + // Page numbers + ..._buildPageNumbers(), + + // Next page + IconButton( + icon: Icon(Icons.chevron_right, size: 18), + onPressed: completedCurrentPage < totalCompletedPages + ? () => setState(() => completedCurrentPage++) + : null, + color: completedCurrentPage < totalCompletedPages + ? Color(0xFF2E7D6E) + : Colors.grey, + tooltip: 'Next page', + ), + + // Last page + IconButton( + icon: Icon(Icons.last_page, size: 18), + onPressed: completedCurrentPage < totalCompletedPages + ? () => setState( + () => completedCurrentPage = totalCompletedPages, + ) + : null, + color: completedCurrentPage < totalCompletedPages + ? Color(0xFF2E7D6E) + : Colors.grey, + tooltip: 'Last page', + ), + ], + ), + ], + ), + ); + } + + // Helper method to build page number buttons + List _buildPageNumbers() { + List pageButtons = []; + + // Show max 5 page numbers + int startPage = (completedCurrentPage - 2).clamp(1, totalCompletedPages); + int endPage = (startPage + 4).clamp(1, totalCompletedPages); + + // Adjust start if we're near the end + if (endPage - startPage < 4) { + startPage = (endPage - 4).clamp(1, totalCompletedPages); + } + + for (int i = startPage; i <= endPage; i++) { + pageButtons.add( + Padding( + padding: EdgeInsets.symmetric(horizontal: 2), + child: InkWell( + onTap: () => setState(() => completedCurrentPage = i), + child: Container( + width: 32, + height: 32, + alignment: Alignment.center, + decoration: BoxDecoration( + color: completedCurrentPage == i + ? Color(0xFF2E7D6E) + : Colors.transparent, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: completedCurrentPage == i + ? Color(0xFF2E7D6E) + : Color(0xFFE2E8F0), + ), + ), + child: Text( + '$i', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: completedCurrentPage == i + ? Colors.white + : Color(0xFF64748B), + ), + ), + ), + ), + ), + ); + } + + return pageButtons; + } } diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec11.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec11.dart new file mode 100644 index 0000000..6a67f06 --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec11.dart @@ -0,0 +1,5520 @@ +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 quickQuotationKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + String? selectedFileNames; + String? lastPickedFile; + + 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; + bool isLoadingInsurers = false; + bool isLoadingInsuranceType = false; + bool isLoadingBroker = false; + bool isLoadingPaymentMode = false; + bool isLoadingStaffEnqAssign = false; + bool isLoadingStaffList = false; + bool isLoadingVehicleType = false; + bool isLoadingAgentList = false; + dynamic roleId; + bool _showFilterRow = false; + + Map rowExpanded = {}; + Map controllers = {}; + List tabHeader = [ + 'startDate', + 'endDate', + 'name', + 'email', + 'mobile', + 'code', + 'address', + 'regNo', + 'remarks', + ]; + + String formRebuildKey = UniqueKey().toString(); + + Map isEditingRow = {}; + Map> rowControllers = {}; + late ProviderSubscription refreshSub; + + //---------------------- 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; + + // int selectedIndex = 1; + int selectedIndex = 0; + int autoRefreshIndex = 0; + + final List enquiryTabs = ["In Progress", "Completed"]; + + 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; + Timer? timer; + int selectedSeconds = 30; + bool isActionable = false; + bool _listened = false; + + //---------------------- Assign Staff Ends -------------------------------- // + //---------------------- Enquiry TAB Initializations Ends -------------------------------- // + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + // timer = Timer.periodic(Duration(seconds: 10), (Timer t) => callRefresh()); //static + + startTimer(); + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + _initializeToken(); + loadPreferences(); + 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'); + quickQuotationKey = prefs.getString('QuoteKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + print('quickQuotationKey'); + + 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); + } + + ref.listen(enquiryRefreshProvider, (prev, next) { + print('quickQuotationKey1'); + if (next == true) { + print('quickQuotationKey2 - $next'); + callRefresh(); + ref.read(enquiryRefreshProvider.notifier).state = false; // reset + } + }); + }); + + getVehicleType(); + getInsurers(); + getInsuranceType(); + getBroker(); + getPaymentMode(); + + refreshSub = ref.listenManual(enquiryRefreshProvider, ( + previous, + next, + ) { + if (next == true) { + print('refresh triggered Quick Creation'); + callRefresh(); + + ref.read(enquiryRefreshProvider.notifier).state = false; + } + }); + } + + Future loadPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + setState(() { + isActionable = prefs.getBool('isActionable') ?? false; + selectedSeconds = prefs.getInt('selectedSeconds') ?? 30; + + _showFilterRow = true; + }); + + startTimer(); // restart timer using stored value + } + + Future resetPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + await prefs.remove('isActionable'); + await prefs.remove('selectedSeconds'); + + setState(() { + // isActionable = false; // default + selectedSeconds = 30; // default + }); + + startTimer(); // restart timer with default value + + print("Storage cleared & reset completed"); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void startTimer() { + timer?.cancel(); // stop existing timer + + timer = Timer.periodic(Duration(seconds: selectedSeconds), (Timer t) { + callRefresh(); + }); + } + + void callRefresh() { + print('Call Refresh'); + if (!isActionable) { + print('Call Refresh isActionable'); + autoRefrshfilterDateRange(); + } + } + + 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() { + refreshSub.close(); + _horizontalScrollController.dispose(); + // ... dispose other controllers ... + super.dispose(); + } + + Future autoRefrshfilterDateRange() async { + print('autorefrshfilterDateRange'); + // setState(() async { + final prefs = await SharedPreferences.getInstance(); + + // _formKey.currentState?.reset(); + + final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0; + print('autorefrshfilterDateRange 1'); + getStaffList( + // managerId, + parsedManagerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + autoRefreshIndex = selectedIndex; + print('autoRefreshIndex - $autoRefreshIndex'); + _searchStaffController.clear(); + print('refrshfilterDateRange 2'); + } + + 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(() { + isLoadingInsurers = 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(() { + isLoadingInsurers = false; + }); + } + } + + Future getInsuranceType() async { + print('getClaimList called'); + setState(() { + isLoadingInsuranceType = 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(() { + isLoadingInsuranceType = false; + }); + } + } + + Future getBroker() async { + print('getBroker called'); + setState(() { + isLoadingBroker = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('Broker'); + + if (response['status'] == 200) { + print('getBroker - ${response['data']}'); + setState(() { + getBrokerData = List>.from(response['data']); + print('API Data - $getBrokerData'); + + filteredBrokerData = List.from(getBrokerData); + print('originalData - $filteredBrokerData'); + }); + } else { + getBrokerData = []; + filteredBrokerData = []; + } + } catch (e) { + print('Exception occurred: $e'); + } finally { + setState(() { + isLoadingBroker = false; + }); + } + } + + Future getPaymentMode() async { + print('getPaymentMode called'); + setState(() { + isLoadingPaymentMode = 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(() { + isLoadingPaymentMode = false; + }); + } + } + + Future getStaffDetailsForEnquiryAssignment(int id) async { + print('getStaffDetailsForEnquiryAssignment called By handler'); + setState(() { + isLoadingStaffEnqAssign = 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(() { + isLoadingStaffEnqAssign = false; + }); + } + } + + Future getStaffList( + int managerId, + role, { + String fromDate = '', + String toDate = '', + String SelectedStatus = '', + String SelectedStaffId = '', + }) async { + print('DAJJshA72 => Fns called => $managerId | $role'); + setState(() { + isLoadingStaffList = true; + }); + + dynamic fromDt; + dynamic toDt; + + final fromDateVal = controllers['startDate']?.text ?? ''; + final toDateVal = controllers['endDate']?.text ?? ''; + final dateFormat = DateFormat('dd-MM-yyyy'); + + if (dashboardKey == 'fromDashboard' && SelectedStatus == "PrevPending") { + // For PREV PENDING → set date range until yesterday + final today = DateTime.now(); + + // Change to dd-MM-yyyy + final dateFormat = DateFormat('dd-MM-yyyy'); + + final yesterday = today.subtract(Duration(days: 1)); + + fromDt = dateFormat.format(yesterday.subtract(Duration(days: 2))); + toDt = dateFormat.format(yesterday); + + print('PrevPending FROM TO - $fromDt - $toDt'); + } else if (dashboardKey == 'fromDashboard' && + SelectedStatus != "PrevPending" && + SelectedStatus != '') { + // For PREV PENDING → set date range until yesterday + final today = DateTime.now(); + + // Change to dd-MM-yyyy + final dateFormat = DateFormat('dd-MM-yyyy'); + + fromDt = dateFormat.format(today); + toDt = dateFormat.format(today); + + print('PrevPending FROM TO - $fromDt - $toDt'); + } else if (dashboardKey != 'fromDashboard' && + fromDateVal == '' && + toDateVal == '') { + print("ENQ LIST PAGE"); + + final today = DateTime.now(); + fromDt = dateFormat.format(today.subtract(Duration(days: 2))); + // fromDt = dateFormat.format(today); + toDt = dateFormat.format(today); + + print('Enq FROM TO - $fromDt - $toDt'); + } else { + print('Enq 1- $fromDt - $toDt'); + fromDt = fromDateVal; + toDt = toDateVal; + } + print('Enq 2- $fromDt - $toDt'); + String finalStatus = SelectedStatus; + print('finalStatus1 - $finalStatus'); + + // if ((finalStatus != '') && + // (SelectedStatus != 'Completed') && + // (autoRefreshIndex == 0)) { + // finalStatus = 'OtherThenCompleted'; + // print('finalSt1 - $finalStatus'); + // } else if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) { + // finalStatus = 'Completed'; + // print('finalSt2 - $finalStatus'); + // } else if (SelectedStatus == 'PrevPending') { + // finalStatus = ''; + // print('finalSt3 - $finalStatus'); + // } else { + // finalStatus = 'OtherThenCompleted'; + // print('finalSt4 - $finalStatus'); + // // finalStatus = SelectedStatus; // keep original + // } + // print('finalStatus2 - $finalStatus'); + // print('DAJJshA79SelectedStatus - $SelectedStatus'); + + try { + final response = await apiService.fetchEnquiryList( + managerId, + userId, + role, + fromDate: fromDt, + toDate: toDt, + // selectedStatus: (finalStatus != '' && finalStatus != 'PrevPending') + // ? finalStatus + // : '', + selectedStatus: + (SelectedStatus != '' && SelectedStatus != 'PrevPending') + ? 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(() { + // if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) { + // selectedIndex = 1; + // SelectedStatus = 'Completed'; + // } else { + // selectedIndex = 0; + // SelectedStatus = 'OtherThenCompleted'; + // } + 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(() { + isLoadingStaffList = 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['agent_code'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['reg_no'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['enquiry_status'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['assigned_to_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insured_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_short_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"); + (); + } + + Future handleDeleteEnq(Map item) async { + final id = int.tryParse(item['id'].toString()) ?? 0; + idPrimary = item['id']; + + print("handleDeleteEnq - $idPrimary"); + final res = await apiService.deleteEnquiy(id, 0); + + if (res['status'] == 'success') { + autoRefrshfilterDateRange(); + } + } + + 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']; + selectedBroker = item['broker_id']; + 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: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item['name'].toString(), + style: GoogleFonts.inter( + fontSize: 12, + color: Colors.black, + ), + ), + Text( + "(${item['today_assigned']} / ${item['total_pending']})", + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.blueGrey.shade200, + ), + ), + ], + ), + ); + }, + ), + + 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['short_name'].toString().toLowerCase().contains( + filter.toLowerCase(), + ); + }).toList(); + }, + + itemAsString: (val) => val['short_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['short_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['short_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 Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Broker', style: _subLabelTimeStyle), + SizedBox(height: 5), + 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: Color(0XFF6366F1), + ), + 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: Color(0XFF6366F1), + width: 0.1, + ), + ), + 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: "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(() { + isLoadingVehicleType = true; + }); + + try { + final response = await apiService.fetchMasterDropDown('vehicleType'); + + 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(() { + isLoadingVehicleType = false; + }); + } + } + + Future getAgentList(id) async { + print('getAgentListData called'); + setState(() { + isLoadingAgentList = 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(() { + isLoadingAgentList = 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: ThemedFormField( + 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- ]')), + ], + onChanged: (val) async { + print('Vehicle - $val'); + final res = await apiService.CheckDuplicate(context, val); + print('Vehicle res- $res'); + + if (res["message"] != null && res["message"] != "") { + ToastHelper.showErrorToast(context, res['message']); + setState(() { + fieldErrors['regNo'] = res['message']; // store API message + }); // 🔥 store backend message + } else { + setState(() { + fieldErrors['regNo'] = res['message']; // store API message + }); + } + }, + validator: (value) { + // Show backend validation message + if (fieldErrors['regNo'] != null && + fieldErrors['regNo']!.isNotEmpty) { + return fieldErrors['regNo']; // show API message + } + + return Validators.requiredField(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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/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: 'api/agent/downloadAgentCertificateFile?agent_id=$selectedId', + apiUrl: + 'api/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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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), + buildBroker(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), + }, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: IconButton( + tooltip: 'Close', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + icon: const Icon( + Icons.close, + color: Colors.indigo, + size: 28, // smaller icon + ), + splashRadius: 14, // ripple radius + onPressed: () { + setState(() { + // isActionable = false; + bool isOpen = rowExpanded[id] ?? false; + + rowExpanded.clear(); // close all + + // rowExpanded[id] = !(rowExpanded[id] ?? false); + }); + }, + ), + ), + ], + ); + } + + 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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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 = + // 'api/agent/downloadAgentIncentiveFile?id=$selectedId'; + final path = + 'api/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!"); + setState(() { + docUploadedFile = null; + }); + refrshfilterDateRange(); + setState(() {}); + 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, + key: ValueKey(formRebuildKey), + onSubmit: (dataSet) async { + print('Check checking dataSet $dataSet'); + try { + await createUserData(dataSet); // <-- API call here + print("Enquiry saved successfully"); + // RESET FORM + raiseEnqKey.currentState?.resetFormFields(); + // REBUILD THE ENTIRE FORM + setState(() { + formRebuildKey = UniqueKey().toString(); + }); + } 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( + formKey: _formKey, + // key: ValueKey(SelectedStatus ?? ''), + role: roleId, + id: userId, + selectedStatusVal: SelectedStatus, + selectedStaffId: SelectedStaffId, + onFilterStaff: (val) { + print('Selected Filterd STAFF Id - $val'); + SelectedStaffId = val; + }, + // selectedStaffId: SelectedStaffId, + startController: controllers['startDate']!, + endController: controllers['endDate']!, + onStatusChanged: (val) { + SelectedStatus = val; // update parent + }, + + 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; + }); + }, + ), + + SizedBox(width: 10), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Color(0xFFE2E8F0), width: 1), + ), + child: Row( + children: [ + Container( + // color: Colors.yellow.shade50, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + isActionable ? "OFF" : "ON", + style: GoogleFonts.poppins( + fontSize: 10, + color: !isActionable ? Colors.green : Colors.grey, + ), + ), + SizedBox( + height: 10, + width: 30, + child: Padding( + padding: EdgeInsets.zero, + child: Transform.scale( + scale: 0.4, + child: Switch( + value: !isActionable, + onChanged: (v) async { + bool newValue = !v; + setState(() => isActionable = newValue); + + final prefs = + await SharedPreferences.getInstance(); + await prefs.setBool( + 'isActionable', + newValue, + ); + }, + ), + ), + ), + ), + ], + ), + ), + Tooltip( + message: 'Set AutoRefresh Timer', + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(30), + border: Border( + left: BorderSide( + color: Color(0xFFE2E8F0), + width: 2, + ), + // right: BorderSide( + // color: Color(0xFFE2E8F0), + // width: 2, + // ), + ), + ), + height: 30, + child: DropdownButton( + value: selectedSeconds, + underline: SizedBox(), + items: [30, 45, 60].map((e) { + return DropdownMenuItem( + value: e, + child: Text( + "$e sec", + style: GoogleFonts.poppins(fontSize: 11), + ), + ); + }).toList(), + onChanged: (value) async { + setState(() { + selectedSeconds = value!; + startTimer(); // restart timer with new value + }); + + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt('selectedSeconds', value!); + }, + ), + ), + ), + // + // InkWell( + // onTap: () { + // resetPreferences(); + // }, + // child: Icon(Icons.refresh, size: 18), + // ), + ], + ), + ), + SizedBox(width: 10), + MouseRegion( + onEnter: (_) { + // setState(() { + // isActionable = true; + // }); + }, + onExit: (_) { + // setState(() { + // // isActionable = false; + // isActionable = _searchStaffController.text.isNotEmpty; + // }); + }, + child: 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_short_name", + "reg_no", + "insured_name", + "updated_on", + "premium_amount", + "payment_mode", + "policy_number", + "status", + ], + ), + ], + ), + ), + + // SizedBox(height: 5), + SizedBox(child: _buildStyledTabs()), + 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, + 'To be assigned': true, + 'Assigned': true, + 'In progress': 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', + // 'Policy Created': 'Policy Created', + 'To be assigned': 'To Be Assigned', + 'Assigned': 'Assigned', + 'In progress': 'In Progress', + }; + + List> get filteredDataByTab { + if (selectedIndex == 0) { + // Progress enquiries + final sortedData = [...filteredData] + ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + if (sortedData.isEmpty) return []; + return sortedData.where((e) { + return [ + "To be assigned", + "Assigned", + "In progress", + ].contains(e['enquiry_status']); + }).toList(); + } else { + final sortedData = [...filteredData] + ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + if (sortedData.isEmpty) return []; + // Completed enquiries + return sortedData.where((e) { + return e['enquiry_status'] == "Completed"; + }).toList(); + } + } + + // Add group enquiries by status + Map>> get _groupedEnquiries { + final Map>> grouped = {}; + + if (selectedIndex == 0) { + grouped["To be assigned"] = []; + grouped["Assigned"] = []; + grouped["In progress"] = []; + } else { + grouped["Completed"] = []; + } + + for (var item in filteredDataByTab) { + final status = (item['enquiry_status'] ?? '').toString(); + if (grouped.containsKey(status)) { + grouped[status]!.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), + + 'To be assigned': const Color(0xFFEFF6FF), // Blue 50 + 'Assigned': const Color(0xFFFEF9C3), // Cyan 50 + 'In progress': const Color(0xFFF0FDF4), // Green 50 + 'Completed': 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 + + 'To be assigned': const Color(0xFFDBEAFE), // Blue 100 + 'Assigned': const Color(0xFFFEF3C7), // Cyan 100 + 'In progress': const Color(0xFFDCFCE7), // Green 100 + 'Completed': 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: 2), + child: Row( + children: [ + Icon( + isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 22, + color: Color(0xFF5A6C7D), + ), + SizedBox(width: 8), + Text( + // '${config['icon']} $status', + _formatStatus(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') ...[ + if (items.isNotEmpty) ...[ + 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) + // + // 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'] ?? '-'; + String status = item['enquiry_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( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 60, + child: Text( + item['insurer_short_name'] != null + ? item['insurer_short_name'] + : '-', + + style: _dataBoldthm2, + ), + ), + Tooltip( + message: 'Broker : ${item['broker_name']}', + child: Text( + item['broker_name'] != null + ? _truncateText(item['broker_name'], 8) + : '-', + + style: _dataBoldthm4, + ), + ), + ], + ), + ), + + DataCell( + SizedBox( + width: 90, + child: Tooltip( + message: item['insured_name'], + child: Text( + // item['insured_name'] != null + // ? _truncateText(item['insured_name'], 15) + // : '-', + 'Insured Name', + 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: 80.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, + ), + ), + ), + ), + ), + + if (item['enquiry_status'] != 'Completed' && + roleId == 'manager') ...[ + IconButton( + padding: EdgeInsets.zero, // remove outer padding + constraints: BoxConstraints( + // reduce clickable area + minWidth: 24, + minHeight: 24, + ), + icon: Icon( + Icons.delete, + color: Colors.red.shade400, + size: 15, + ), + + tooltip: "Delete Enquiry", + onPressed: () { + setState(() {}); + handleDeleteEnq(item); + }, + ), + ], + ], + ), + ), + ), + + DataCell( + SizedBox( + width: 120, + + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item['enquiry_status'] != 'Assigned') ...[ + Material( + color: Colors.white, + child: InkWell( + // onTap: null, + hoverColor: Color(0xFFEAF6F4), + onTap: (item['enquiry_status'] == 'Completed') + ? () async { + final prefs = + await SharedPreferences.getInstance(); + + // ✅ Remove old value (if any) + await prefs.remove('enqStaffDataId'); + + // ✅ Save the new id + await prefs.setString( + 'enqStaffDataId', + id.toString(), + ); + + // Read it back if needed + final dynamic? enqStaffDataId = prefs.getString( + 'enqStaffDataId', + ); + + // ✅ Update provider too + ref + .read(quotationStaffIdProvider.notifier) + .state = + enqStaffDataId; + + buildPolicyCreatedStatusActions( + context, + status, + item['id'], + ); + } + : null, + 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['enquiry_status'] ?? '-', + // 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), + ), + ), + ], + if ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] != null)) ...[ + TextButton( + onPressed: () async { + final res = await apiService.updateEnquiryInProgress(id); + if (res['status'] == 'success') { + refrshfilterDateRange(); + } + print('updateEnquiryInProgress - $res'); + }, + + child: Tooltip( + message: 'Click to Proceed', + waitDuration: const Duration( + milliseconds: 500, + ), // optional + showDuration: const Duration(seconds: 2), + child: Text( + 'PROCEED', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.green, + ), + ), + ), + ), + ], + + if ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null)) ...[ + TextButton( + onPressed: () 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 Create Quote', + waitDuration: const Duration( + milliseconds: 500, + ), // optional + showDuration: const Duration(seconds: 2), + child: Text( + 'Create Quote', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.blue, + ), + ), + ), + ), + ], + ], + ), + ), + ), + // 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['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? 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['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? 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['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? Text('-') + : Row( + children: [ + if ((((item['enquiry_status'] == 'Completed') && + (item['quotation_id'] != null)))) ...[ + // Text(item['policy_pdf_file_name']), + Tooltip( + message: 'Download Policy File', + child: InkWell( + onTap: () => apiService.downloadFile( + apiUrl: + 'api/policy/downloadPolicyFile?policy_id=${item["policy_id"]}&file_type=policy_pdf', + apiId: item["policy_id"], + localFile: null, + fileName: item['policy_pdf_file_name'], + ), + + child: Icon( + Icons.file_download, + size: 14, + color: Colors.blueAccent, + ), + ), + ), + ], + + 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( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Upload Policy PDF', + style: _dataBold, + ), + SizedBox(height: 10), + ThemedUploadField( + // key: ValueKey(selectedFileNames), + // 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 (lastPickedFile == fileName) { + print( + "❌ Cancel clicked. Ignoring old file.", + ); + ToastHelper.showErrorToast( + context, + 'Please upload a new file. No previous file exists.', + ); + setState(() { + print('Clear Fields'); + selectedFileNames = + null; // clears the field + docUploadedFile = null; + print('Clear Fields - 1'); + }); + // Navigator.of(context).pop(); + // refrshfilterDateRange(); + return; + } + + lastPickedFile = fileName; + + // setState(() { + // docUploadedFile = null; + // selectedFileNames = null; + // }); + + // ❗ User canceled picker + if (file == null || + fileName == null || + fileName.isEmpty || + file.size == 0) { + print( + "❌ File selection canceled. No upload.", + ); + return; + } + + 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["policy_id"], // ONLY while updating + }, + ); + Navigator.of(context).pop(); + // if (selectedBrokerName == 'Nhance') { + // handleUploadPolicy(); + // } + }, + ), + ], + ), + ), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: + 'Click and Upload Policy PDF', // 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, + ), + ), + ), + ), + ), + ], + ), + ), + ), + + // 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)), + DataColumn(label: Text('Status', 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)), + ]; + } + + // 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', 110), + _buildHeaderCell('Status', 100), + // _buildHeaderCell('Assigned Date', 100), + // _buildHeaderCell('Premium', 100), + _buildHeaderCell('Payment Mode', 130), + _buildHeaderCell('Plan Type', 130), + + _buildHeaderCell('Policy', 120), + ], + ), + ); + } + + 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, + ), + ), + ), + ], + ), + ); + } + + Widget _buildStyledTabs() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 4), + decoration: const BoxDecoration( + // color: Colors.white, + // color: Colors.amber, + // border: Border(bottom: BorderSide(color: Color(0xFFE5E5E5), width: 1)), + ), + child: Row( + children: List.generate(enquiryTabs.length, (index) { + final bool isSelected = selectedIndex == index; + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + setState(() { + print('TAPPED TAB'); + selectedIndex = index; + + // if (selectedIndex == '1') { + // getStaffList( + // managerId, + // roleId, + // SelectedStatus: 'Completed', + // ); + // } else { + // getStaffList(managerId, roleId); + // } + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), + + // margin: const EdgeInsets.only(right: 24), + decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(6), + color: isSelected + // ? Color(0xFFF1F5F9) + ? const Color(0xFF2E7D6E).withOpacity(0.08) + : Colors.transparent, + border: Border.all( + color: isSelected + ? Colors.transparent + : Colors.blueGrey.shade50, + width: 1, + ), + ), + child: Column( + // mainAxisSize: MainAxisSize.min, + children: [ + Text( + enquiryTabs[index], + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w400, + color: isSelected + ? const Color(0xFF2E7D6E) + : Colors.black87, + ), + ), + + const SizedBox(height: 1), + + // ▬▬ Underline Indicator ▬▬ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + height: 2, + width: isSelected ? 90 : 0, + decoration: BoxDecoration( + color: const Color(0xFF2E7D6E), + borderRadius: BorderRadius.circular(50), + ), + ), + ], + ), + ), + ), + ); + }), + ), + ); + } + + 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 _formatStatus(String status) { + return status + .split(' ') + .map( + (word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1).toLowerCase() + : '', + ) + .join(' '); + } + + 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 : '-'; + } + } +} diff --git a/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec12.dart b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec12.dart new file mode 100644 index 0000000..610e96e --- /dev/null +++ b/lib/presentation/screens/staff/Enquiry/enquiry_inline_list_dec12.dart @@ -0,0 +1,3282 @@ +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 '../../../widgets/custom_Stdate_EnDate_Filter.dart'; +import '../assignStaff.dart'; +import 'enquiry_inline_SubRow.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 quickQuotationKey; + dynamic SelectedStatus; + dynamic SelectedStaffId; + String? selectedFileNames; + String? lastPickedFile; + + 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 = []; + + List> inProgressData = []; + List> completedData = []; + + bool isLoading = false; + bool isLoadingInsurers = false; + bool isLoadingInsuranceType = false; + bool isLoadingBroker = false; + bool isLoadingPaymentMode = false; + bool isLoadingStaffEnqAssign = false; + bool isLoadingStaffList = false; + bool isLoadingVehicleType = false; + bool isLoadingAgentList = false; + dynamic roleId; + bool _showFilterRow = false; + + Map rowExpanded = {}; + Map controllers = {}; + List tabHeader = [ + 'startDate', + 'endDate', + 'name', + 'email', + 'mobile', + 'code', + 'address', + 'regNo', + 'remarks', + ]; + + String formRebuildKey = UniqueKey().toString(); + + Map isEditingRow = {}; + Map> rowControllers = {}; + late ProviderSubscription refreshSub; + + //---------------------- 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; + + // int selectedIndex = 1; + int selectedIndex = 0; + int autoRefreshIndex = 0; + + final List enquiryTabs = ["In Progress", "Completed"]; + + 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>> + dropDownSelectPaymentModeKey = + GlobalKey>>(); + //---------------------- Assign Staff starts -------------------------------- // + + final GlobalKey>> + dropDownKeyInsurerEnqAsgn = + GlobalKey>>(); + + final GlobalKey>> dropDownKeyBroker = + GlobalKey>>(); + + List> getStaffDetailsDataEnqAsgn = []; + List> filteredStaffDataEnqAsgn = []; + + String? selectedStaff; + String? selectedRegNum; + String? selectedStaffName; + + List> getBrokerData = []; + List> filteredBrokerData = []; + String? selectedBroker; + + final ScrollController _horizontalScrollController = ScrollController(); + // late Timer timer; + Timer? timer; + int selectedSeconds = 30; + bool isActionable = false; + bool _listened = false; + + //---------------------- Assign Staff Ends -------------------------------- // + //---------------------- Enquiry TAB Initializations Ends -------------------------------- // + + @override + void initState() { + super.initState(); + + apiService = ApiService(); + // timer = Timer.periodic(Duration(seconds: 10), (Timer t) => callRefresh()); //static + + startTimer(); + for (String field in tabHeader) { + controllers[field] = TextEditingController(); + } + _initializeToken(); + loadPreferences(); + 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'); + quickQuotationKey = prefs.getString('QuoteKeyProvider'); + final dashboardStatus = prefs.getString('dashboardStatusProvider'); + final dashboardStaffId = prefs.getString('dashboardStaffIdProvider'); + + print('handlerIdENQ - $handlerId'); + print("C72 => r : $roleId | uId: $userId !mID : $managerId "); + print('quickQuotationKey'); + + 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); + } + + ref.listen(enquiryRefreshProvider, (prev, next) { + print('quickQuotationKey1'); + if (next == true) { + print('quickQuotationKey2 - $next'); + callRefresh(); + ref.read(enquiryRefreshProvider.notifier).state = false; // reset + } + }); + }); + + getPaymentMode(); + + refreshSub = ref.listenManual(enquiryRefreshProvider, ( + previous, + next, + ) { + if (next == true) { + print('refresh triggered Quick Creation'); + callRefresh(); + + ref.read(enquiryRefreshProvider.notifier).state = false; + } + }); + getInsuranceType(); + } + + Future loadPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + setState(() { + isActionable = prefs.getBool('isActionable') ?? false; + selectedSeconds = prefs.getInt('selectedSeconds') ?? 30; + + _showFilterRow = true; + }); + + startTimer(); // restart timer using stored value + } + + Future resetPreferences() async { + final prefs = await SharedPreferences.getInstance(); + + await prefs.remove('isActionable'); + await prefs.remove('selectedSeconds'); + + setState(() { + // isActionable = false; // default + selectedSeconds = 30; // default + }); + + startTimer(); // restart timer with default value + + print("Storage cleared & reset completed"); + } + + Future _initializeToken() async { + _token = await AuthService.getToken(); + print("APISERTOKEN - $_token"); + } + + void startTimer() { + timer?.cancel(); // stop existing timer + + timer = Timer.periodic(Duration(seconds: selectedSeconds), (Timer t) { + callRefresh(); + }); + } + + void callRefresh() { + print('Call Refresh'); + if (!isActionable) { + print('Call Refresh isActionable'); + autoRefrshfilterDateRange(); + } + } + + 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() { + refreshSub.close(); + _horizontalScrollController.dispose(); + // ... dispose other controllers ... + super.dispose(); + } + + Future autoRefrshfilterDateRange() async { + print('autorefrshfilterDateRange'); + // setState(() async { + final prefs = await SharedPreferences.getInstance(); + + // _formKey.currentState?.reset(); + + final int parsedManagerId = int.tryParse(managerId.toString()) ?? 0; + print('autorefrshfilterDateRange 1'); + getStaffList( + // managerId, + parsedManagerId, + roleId, + SelectedStatus: SelectedStatus ?? '', + SelectedStaffId: SelectedStaffId ?? '', + ); + autoRefreshIndex = selectedIndex; + print('autoRefreshIndex - $autoRefreshIndex'); + _searchStaffController.clear(); + print('refrshfilterDateRange 2'); + } + + 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 getPaymentMode() async { + print('getPaymentMode called'); + setState(() { + isLoadingPaymentMode = 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(() { + isLoadingPaymentMode = false; + }); + } + } + + Future getInsuranceType() async { + print('getClaimList called'); + setState(() { + isLoadingInsuranceType = 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(() { + isLoadingInsuranceType = false; + }); + } + } + + Future getStaffList( + int managerId, + role, { + String fromDate = '', + String toDate = '', + String SelectedStatus = '', + String SelectedStaffId = '', + }) async { + print('DAJJshA72 => Fns called => $managerId | $role'); + setState(() { + isLoadingStaffList = true; + }); + + dynamic fromDt; + dynamic toDt; + + final fromDateVal = controllers['startDate']?.text ?? ''; + final toDateVal = controllers['endDate']?.text ?? ''; + final dateFormat = DateFormat('dd-MM-yyyy'); + + if (dashboardKey == 'fromDashboard' && SelectedStatus == "PrevPending") { + // For PREV PENDING → set date range until yesterday + final today = DateTime.now(); + + // Change to dd-MM-yyyy + final dateFormat = DateFormat('dd-MM-yyyy'); + + final yesterday = today.subtract(Duration(days: 1)); + + fromDt = dateFormat.format(yesterday.subtract(Duration(days: 2))); + toDt = dateFormat.format(yesterday); + + print('PrevPending FROM TO - $fromDt - $toDt'); + } else if (dashboardKey == 'fromDashboard' && + SelectedStatus != "PrevPending" && + SelectedStatus != '') { + // For PREV PENDING → set date range until yesterday + final today = DateTime.now(); + + // Change to dd-MM-yyyy + final dateFormat = DateFormat('dd-MM-yyyy'); + + fromDt = dateFormat.format(today); + toDt = dateFormat.format(today); + + print('PrevPending FROM TO - $fromDt - $toDt'); + } else if (dashboardKey != 'fromDashboard' && + fromDateVal == '' && + toDateVal == '') { + print("ENQ LIST PAGE"); + + final today = DateTime.now(); + fromDt = dateFormat.format(today.subtract(Duration(days: 2))); + // fromDt = dateFormat.format(today); + toDt = dateFormat.format(today); + + print('Enq FROM TO - $fromDt - $toDt'); + } else { + print('Enq 1- $fromDt - $toDt'); + fromDt = fromDateVal; + toDt = toDateVal; + } + print('Enq 2- $fromDt - $toDt'); + String finalStatus = SelectedStatus; + print('finalStatus1 - $finalStatus'); + + try { + final response = await apiService.fetchEnquiryList( + managerId, + userId, + role, + fromDate: fromDt, + toDate: toDt, + // selectedStatus: (finalStatus != '' && finalStatus != 'PrevPending') + // ? finalStatus + // : '', + selectedStatus: + (SelectedStatus != '' && SelectedStatus != 'PrevPending') + ? 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); + + originalData = List>.from(data); + + // Split data immediately after fetching + inProgressData = originalData + .where((item) => item['enquiry_status'] != 'Completed') + .toList(); + + print('inProgressData - $inProgressData'); + + completedData = originalData + .where((item) => item['enquiry_status'] == 'Completed') + .toList(); + print('completedData - $completedData'); + + // filteredData now holds the currently displayed tab's data (without search) + filteredData = (selectedIndex == 0) + ? inProgressData + : completedData; + } 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(() { + isLoadingStaffList = false; + }); + } + } + + void filterData(String query) { + print("FilterDAta - $query"); + setState(() { + final sourceList = (selectedIndex == 0) ? inProgressData : completedData; + + filteredData = sourceList.where((item) { + // final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive"; + + return (item['agent_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['agent_code'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['reg_no'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['enquiry_status'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['assigned_to_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insured_name'] ?? '-').toLowerCase().contains( + query.toLowerCase(), + ) || + (item['insurer_short_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"); + (); + } + + Future handleDeleteEnq(Map item) async { + final id = int.tryParse(item['id'].toString()) ?? 0; + idPrimary = item['id']; + + print("handleDeleteEnq - $idPrimary"); + final res = await apiService.deleteEnquiy(id, 0); + + if (res['status'] == 'success') { + autoRefrshfilterDateRange(); + } + } + + 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']; + selectedBroker = item['broker_id']; + 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(); + + setState(() => rowExpanded.clear()); + } + + 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) { + 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); + }); + } + + //---------------------- 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; + } + + // 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"); + } + } + + 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!"); + } + + // 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!"); + setState(() { + docUploadedFile = null; + }); + refrshfilterDateRange(); + setState(() {}); + 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( + 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, + key: ValueKey(formRebuildKey), + onSubmit: (dataSet) async { + print('Check checking dataSet $dataSet'); + try { + await createUserData(dataSet); // <-- API call here + print("Enquiry saved successfully"); + // RESET FORM + raiseEnqKey.currentState?.resetFormFields(); + // REBUILD THE ENTIRE FORM + setState(() { + formRebuildKey = UniqueKey().toString(); + }); + } 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), + ), + ], + ), + ), + ); + } + + 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) { + 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( + formKey: _formKey, + // key: ValueKey(SelectedStatus ?? ''), + role: roleId, + id: userId, + selectedStatusVal: SelectedStatus, + selectedStaffId: SelectedStaffId, + onFilterStaff: (val) { + print('Selected Filterd STAFF Id - $val'); + SelectedStaffId = val; + }, + // selectedStaffId: SelectedStaffId, + startController: controllers['startDate']!, + endController: controllers['endDate']!, + onStatusChanged: (val) { + SelectedStatus = val; // update parent + }, + + 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; + }); + }, + ), + + SizedBox(width: 10), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Color(0xFFE2E8F0), width: 1), + ), + child: Row( + children: [ + Container( + // color: Colors.yellow.shade50, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + isActionable ? "OFF" : "ON", + style: GoogleFonts.poppins( + fontSize: 10, + color: !isActionable ? Colors.green : Colors.grey, + ), + ), + SizedBox( + height: 10, + width: 30, + child: Padding( + padding: EdgeInsets.zero, + child: Transform.scale( + scale: 0.4, + child: Switch( + value: !isActionable, + onChanged: (v) async { + bool newValue = !v; + setState(() => isActionable = newValue); + + final prefs = + await SharedPreferences.getInstance(); + await prefs.setBool( + 'isActionable', + newValue, + ); + }, + ), + ), + ), + ), + ], + ), + ), + Tooltip( + message: 'Set AutoRefresh Timer', + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(30), + border: Border( + left: BorderSide( + color: Color(0xFFE2E8F0), + width: 2, + ), + ), + ), + height: 30, + child: DropdownButton( + value: selectedSeconds, + underline: SizedBox(), + items: [30, 45, 60].map((e) { + return DropdownMenuItem( + value: e, + child: Text( + "$e sec", + style: GoogleFonts.poppins(fontSize: 11), + ), + ); + }).toList(), + onChanged: (value) async { + setState(() { + selectedSeconds = value!; + startTimer(); // restart timer with new value + }); + + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt('selectedSeconds', value!); + }, + ), + ), + ), + ], + ), + ), + SizedBox(width: 10), + MouseRegion( + child: 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_short_name", + "reg_no", + "insured_name", + "updated_on", + "premium_amount", + "payment_mode", + "policy_number", + "status", + ], + ), + ], + ), + ), + + // SizedBox(height: 5), + SizedBox(child: _buildStyledTabs()), + ResponsiveLayout.isMobile(context) + ? _buildDataTable(context) + : 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 = { + 'To be assigned': true, + 'Assigned': true, + 'In progress': true, + }; + + Map statusGroupMap = { + 'To be assigned': 'To Be Assigned', + 'Assigned': 'Assigned', + 'In progress': 'In Progress', + }; + + List> get filteredDataByTab { + if (selectedIndex == 0) { + // Progress enquiries + final sortedData = [...inProgressData] + ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + // final sortedData = [...filteredData] + // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + if (sortedData.isEmpty) return []; + return sortedData.where((e) { + return [ + "To be assigned", + "Assigned", + "In progress", + ].contains(e['enquiry_status']); + }).toList(); + } else { + // final sortedData = [...filteredData] + // ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + final sortedData = [...completedData] + ..sort((a, b) => int.parse(b['id']) - int.parse(a['id'])); + + if (sortedData.isEmpty) return []; + // Completed enquiries + return sortedData; + // return sortedData.where((e) { + // return e['enquiry_status'] == "Completed"; + // }).toList(); + } + } + + // Add group enquiries by status + Map>> get _groupedEnquiries { + final Map>> grouped = {}; + + if (selectedIndex == 0) { + grouped["To be assigned"] = []; + grouped["Assigned"] = []; + grouped["In progress"] = []; + } else { + grouped["Completed"] = []; + } + + for (var item in filteredDataByTab) { + final status = (item['enquiry_status'] ?? '').toString(); + if (grouped.containsKey(status)) { + grouped[status]!.add(item); + } + } + + return grouped; + } + + 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), + + 'To be assigned': const Color(0xFFEFF6FF), // Blue 50 + 'Assigned': const Color(0xFFFEF9C3), // Cyan 50 + 'In progress': const Color(0xFFF0FDF4), // Green 50 + 'Completed': 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 + + 'To be assigned': const Color(0xFFDBEAFE), // Blue 100 + 'Assigned': const Color(0xFFFEF3C7), // Cyan 100 + 'In progress': const Color(0xFFDCFCE7), // Green 100 + 'Completed': const Color(0xFFF0E4FF), // Purple 200 + // 'Policy Created': const Color(0xFFE5CCFF), // Purple 200 + }; + + // 4. Build group header widget + Widget _buildGroupHeader(String status, int count) { + final isExpanded = groupExpanded[status] ?? true; + + return InkWell( + onTap: () { + setState(() { + groupExpanded[status] = !isExpanded; + }); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + child: Row( + children: [ + Icon( + isExpanded ? Icons.arrow_drop_down : Icons.arrow_right, + size: 22, + color: Color(0xFF5A6C7D), + ), + SizedBox(width: 8), + Text( + // '${config['icon']} $status', + _formatStatus(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 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), + ), + child: _buildTableHeader(isDesktop), + ), + + // Scrollable Data Content + Expanded( + child: Container( + decoration: BoxDecoration(color: Colors.white), + 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: [ + if (items.isNotEmpty) ...[ + 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)), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + DataTable( + border: TableBorder.all( + color: Colors.transparent, + width: 0, + ), + + 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( + border: Border.all( + color: Color(0xFFC7D2FE), + ), + + color: Color(0xFFF8F9FF), + ), + padding: const EdgeInsets.symmetric( + vertical: 14.0, + horizontal: 8.0, + ), + // child: _buildSubRow(context, selectId, id), + child: SubRowWidget( + selectId: selectId, + rowId: id, + onSave: handleSave, + onClose: () { + setState(() => rowExpanded.clear()); + }, + + controllers: controllers, + selectedAgent: selectedAgent, + selectedStaff: selectedStaff, + selectedBroker: selectedBroker, + selectedInsurer: selectedInsurer, + + filteredAgentData: filteredAgentData, + filteredStaffDataEnqAsgn: + filteredStaffDataEnqAsgn, + + onStaffChanged: (val) { + setState(() { + selectedStaff = val; + }); + }, + onAgentChanged: (val) { + setState(() { + selectedAgent = val; + }); + }, + onInsurerChanged: (val) { + setState(() { + selectedInsurer = val; + }); + }, + onBrokerChanged: (val) { + setState(() { + selectedBroker = val; + }); + }, + ), + ), + ], + ), + ), + ], + ); + }).toList(), + ], + ); + }).toList(), + ], + ), + ); + } + + DataRow _buildDataRow(Map item, int sno) { + final id = int.tryParse(item['id'].toString()) ?? 0; + final selectId = item['id'].toString() ?? '0'; + + String status = item['enquiry_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( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 60, + child: Text( + item['insurer_short_name'] != null + ? item['insurer_short_name'] + : '-', + + style: _dataBoldthm2, + ), + ), + Tooltip( + message: 'Broker : ${item['broker_name']}', + child: Text( + item['broker_name'] != null + ? _truncateText(item['broker_name'], 8) + : '-', + + style: _dataBoldthm4, + ), + ), + ], + ), + ), + + 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: 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), + ), + ), + ), + ), + ), + + DataCell( + SizedBox( + width: 80.0, + child: Row( + children: [ + Tooltip( + richMessage: WidgetSpan( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Color(0xFFFFFCF2), + borderRadius: BorderRadius.circular(6), + ), + 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, + ), + ), + ), + ), + ), + + if (item['enquiry_status'] != 'Completed' && + roleId == 'manager') ...[ + IconButton( + padding: EdgeInsets.zero, // remove outer padding + constraints: BoxConstraints( + // reduce clickable area + minWidth: 24, + minHeight: 24, + ), + icon: Icon( + Icons.delete, + color: Colors.red.shade400, + size: 15, + ), + + tooltip: "Delete Enquiry", + onPressed: () { + setState(() {}); + handleDeleteEnq(item); + }, + ), + ], + ], + ), + ), + ), + + DataCell( + SizedBox( + width: 120, + + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item['enquiry_status'] != 'Assigned') ...[ + Material( + color: Colors.white, + child: InkWell( + // onTap: null, + hoverColor: Color(0xFFEAF6F4), + onTap: (item['enquiry_status'] == 'Completed') + ? () async { + final prefs = + await SharedPreferences.getInstance(); + + // ✅ Remove old value (if any) + await prefs.remove('enqStaffDataId'); + + // ✅ Save the new id + await prefs.setString( + 'enqStaffDataId', + id.toString(), + ); + + // Read it back if needed + final dynamic? enqStaffDataId = prefs.getString( + 'enqStaffDataId', + ); + + // ✅ Update provider too + ref + .read(quotationStaffIdProvider.notifier) + .state = + enqStaffDataId; + + buildPolicyCreatedStatusActions( + context, + status, + item['id'], + ); + } + : null, + 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['enquiry_status'] ?? '-', + // 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), + ), + ), + ], + if ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] != null)) ...[ + TextButton( + onPressed: () async { + final res = await apiService.updateEnquiryInProgress(id); + if (res['status'] == 'success') { + refrshfilterDateRange(); + } + print('updateEnquiryInProgress - $res'); + }, + + child: Tooltip( + message: 'Click to Proceed', + waitDuration: const Duration( + milliseconds: 500, + ), // optional + showDuration: const Duration(seconds: 2), + child: Text( + 'PROCEED', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.green, + ), + ), + ), + ), + ], + + if ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null)) ...[ + TextButton( + onPressed: () 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 Create Quote', + waitDuration: const Duration( + milliseconds: 500, + ), // optional + showDuration: const Duration(seconds: 2), + child: Text( + 'Create Quote', + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.blue, + ), + ), + ), + ), + ], + ], + ), + ), + ), + + DataCell( + SizedBox( + height: 30, + width: 120, + child: + ((item['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? 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['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? 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']); + }, + ), + ), + ), + ), + + // Policy Number + DataCell( + SizedBox( + height: 30, + width: 90, + child: + ((item['enquiry_status'] == 'To be assigned') || + ((item['enquiry_status'] == 'Assigned') && + (item['quotation_id'] == null))) + ? Text('-') + : Row( + children: [ + if ((((item['enquiry_status'] == 'Completed') && + (item['quotation_id'] != null)))) ...[ + // Text(item['policy_pdf_file_name']), + Tooltip( + message: 'Download Policy File', + child: InkWell( + onTap: () => apiService.downloadFile( + apiUrl: + 'api/policy/downloadPolicyFile?policy_id=${item["policy_id"]}&file_type=policy_pdf', + apiId: item["policy_id"], + localFile: null, + fileName: item['policy_pdf_file_name'], + ), + + child: Icon( + Icons.file_download, + size: 14, + color: Colors.blueAccent, + ), + ), + ), + ], + + 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( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Upload Policy PDF', + style: _dataBold, + ), + SizedBox(height: 10), + ThemedUploadField( + // key: ValueKey(selectedFileNames), + // 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 (lastPickedFile == fileName) { + print( + "❌ Cancel clicked. Ignoring old file.", + ); + ToastHelper.showErrorToast( + context, + 'Please upload a new file. No previous file exists.', + ); + setState(() { + print('Clear Fields'); + selectedFileNames = + null; // clears the field + docUploadedFile = null; + print('Clear Fields - 1'); + }); + // Navigator.of(context).pop(); + // refrshfilterDateRange(); + return; + } + + lastPickedFile = fileName; + + // setState(() { + // docUploadedFile = null; + // selectedFileNames = null; + // }); + + // ❗ User canceled picker + if (file == null || + fileName == null || + fileName.isEmpty || + file.size == 0) { + print( + "❌ File selection canceled. No upload.", + ); + return; + } + + 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["policy_id"], // ONLY while updating + }, + ); + Navigator.of(context).pop(); + // if (selectedBrokerName == 'Nhance') { + // handleUploadPolicy(); + // } + }, + ), + ], + ), + ), + ], + color: Colors.white, + ); + }, + child: Tooltip( + message: + 'Click and Upload Policy PDF', // 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, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + // 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)), + DataColumn(label: Text('Status', 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)), + ]; + } + + // 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', 110), + _buildHeaderCell('Status', 100), + // _buildHeaderCell('Assigned Date', 100), + // _buildHeaderCell('Premium', 100), + _buildHeaderCell('Payment Mode', 130), + _buildHeaderCell('Plan Type', 130), + + _buildHeaderCell('Policy', 120), + ], + ), + ); + } + + 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, + ), + ), + ), + ], + ), + ); + } + + Widget _buildStyledTabs() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 4), + decoration: const BoxDecoration( + // color: Colors.white, + // color: Colors.amber, + // border: Border(bottom: BorderSide(color: Color(0xFFE5E5E5), width: 1)), + ), + child: Row( + children: List.generate(enquiryTabs.length, (index) { + final bool isSelected = selectedIndex == index; + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + setState(() { + print('TAPPED TAB'); + selectedIndex = index; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 10, + ), + + // margin: const EdgeInsets.only(right: 24), + decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(6), + color: isSelected + // ? Color(0xFFF1F5F9) + ? const Color(0xFF2E7D6E).withOpacity(0.08) + : Colors.transparent, + border: Border.all( + color: isSelected + ? Colors.transparent + : Colors.blueGrey.shade50, + width: 1, + ), + ), + child: Column( + // mainAxisSize: MainAxisSize.min, + children: [ + Text( + enquiryTabs[index], + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w400, + color: isSelected + ? const Color(0xFF2E7D6E) + : Colors.black87, + ), + ), + + const SizedBox(height: 1), + + // ▬▬ Underline Indicator ▬▬ + AnimatedContainer( + duration: const Duration(milliseconds: 250), + height: 2, + width: isSelected ? 90 : 0, + decoration: BoxDecoration( + color: const Color(0xFF2E7D6E), + borderRadius: BorderRadius.circular(50), + ), + ), + ], + ), + ), + ), + ); + }), + ), + ); + } + + 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 _formatStatus(String status) { + return status + .split(' ') + .map( + (word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1).toLowerCase() + : '', + ) + .join(' '); + } + + 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 : '-'; + } + } +}