/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useCallback, useEffect, useState } from "react"; import CreateOrderFooter from "./CreateOrderFooter"; import PreviewDetails from "./Step2/PreviewDetails"; // import { step2Schema } from "./ValidationSchema"; import { getMasterList } from "../../../../Api/MastersApi"; import { createBlock, createRoad, createBuilding, searchCustomerByPhone, } from "../../../../Api/CustomerApi"; import { useAppSelector } from "@/Redux/Hooks"; import { createOrder } from "../../../../Api/OrderApi"; import Swal from "sweetalert2"; import { saveStepData, setReduxOrderId, } from "@/Redux/Reducers/CreateOrderSlice"; import { useDispatch } from "react-redux"; import { debounce } from "lodash"; // import { CustomizedSelect as SelectField } from "../../../TextField/CustomizedSelect"; import PhoneInputField from "../../../TextField/PhoneInputField"; import HourGlassLoader from "../../../Loader/Loader"; import { toast } from "react-toastify"; import { validatePhoneNumberByCountry } from "../../../../lib/Helper"; import SelectOptions from "../../../TextField/SelectOptions"; import * as yup from "yup"; import type { OptionType } from "../../../TextField/SelectOptions"; import { OrderFlowType } from "@/Constant/enums"; import { useSearchParams } from "next/navigation"; export interface Option { id: string | number; name: string; code?: string; } type Step2Props = { onSave: (data: any) => void; step: number; setStep: React.Dispatch>; defaultOrderDetails: any; isEditMode?: boolean; }; const isStep2Complete = (data: any): boolean => { if (!data) return false; const blockId = typeof data.blockNo === "object" ? data.blockNo?.id : data.blockNo; const roadId = typeof data.roadNo === "object" ? data.roadNo?.id : data.roadNo; const buildingId = typeof data.buildingNo === "object" ? data.buildingNo?.id : data.buildingNo; return !!( ( data.deliveryMobile && data.customerName && blockId && roadId && buildingId && data.flatNo ) // data.Remarks ); }; const Step2: React.FC = ({ step, setStep, onSave, defaultOrderDetails, }) => { const dispatch = useDispatch(); const searchParams: any = useSearchParams(); const packageDisable: any = searchParams.get("packageDisable"); const [isLoading, setIsLoading] = useState(false); const storedUser = localStorage.getItem("ALL_DATA"); const allData = storedUser ? JSON.parse(storedUser)?.data : null; const completeOrder: any = useAppSelector((state) => state.createOrder); const createOrderData: any = useAppSelector( (state) => state.createOrder )?.step2; const createOrderStep3Data: any = useAppSelector( (state) => state.createOrder )?.step3; const [isBlockLoading, setIsBlockLoading] = useState(false); const [isRoadLoading, setIsRoadLoading] = useState(false); const [isBuildingLoading, setIsBuildingLoading] = useState(false); const [errors, setErrors] = useState>({}); const [orderId, setOrderId] = useState( createOrderData?.customerInputOrderId || "" ); const [deliveryMobile, setDeliveryMobile] = useState( defaultOrderDetails?.destinationMobileNumber || "" ); const [altDeliveryMobile, setAltDeliveryMobile] = useState( defaultOrderDetails?.destinationAlternateNumber || "" ); // State for alternative mobile number const [customerName, setCustomerName] = useState( defaultOrderDetails?.destinationCustomerName || "" ); const [blockNo, setBlockNo] = useState(null); // Use the correct Option type here const [roadNo, setRoadNo] = useState(null); const [buildingNo, setBuildingNo] = useState(null); const [flatNo, setFlatNo] = useState(""); const [remarks, setRemarks] = useState(""); // Add new state for sender_address and destination_address const [senderAddress, setSenderAddress] = useState(""); const [destinationAddress, setDestinationAddress] = useState(""); // Add character counters for sender and destination address const [senderAddressChars, setSenderAddressChars] = useState(255); const [destinationAddressChars, setDestinationAddressChars] = useState(255); const [blocks, setBlocks] = useState([]); const [filteredRoads, setFilteredRoads] = useState([]); const [filteredBuildings, setFilteredBuildings] = useState([]); const [remainingChars, setRemainingChars] = useState(255); // Add a new state to track if form is complete const [isFormComplete, setIsFormComplete] = useState(false); const [selectedCountry, setSelectedCountry] = useState("BH"); // Add ref to track if API call is in progress const isApiCallInProgress = React.useRef(false); const previousFormData = React.useRef(null); // Add ref to track if first API call has been made const isFirstApiCallMade = React.useRef(false); // Add new modal states const [isBlockModalOpen, setIsBlockModalOpen] = useState(false); const [isRoadModalOpen, setIsRoadModalOpen] = useState(false); const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false); // Add new block modal state const [newBlock, setNewBlock] = useState({ code: "", name: "", name_ar: "" }); // Add new road modal state const [newRoad, setNewRoad] = useState({ code: "", name: "", name_ar: "", block_id: "", }); // Add new building modal state const [newBuilding, setNewBuilding] = useState({ code: "", name: "", name_ar: "", block_id: "", road_id: "", }); const step2Schema = yup.object().shape({ orderId: yup.string().optional(), deliveryMobile: yup .string() .matches(/^\+?\d+$/, "Phone number must contain only digits") .test("valid-country-phone", "Invalid phone number", (value) => { if (!value) return false; const digits = value.replace(/^\+/, ""); // Bahrain (+973) if (value.startsWith("+973")) { return digits.length === 11; // +973 + 8 digits = 11 total } // India (+91) if (value.startsWith("+91")) { return digits.length === 12 && /^[6-9]\d{9}$/.test(digits.slice(2)); } // Generic fallback: 8–15 digits return digits.length >= 8 && digits.length <= 15; }) .required("Please enter the receiver mobile no."), // .matches(/^\d{10}$/, "Delivery mobile number must be exactly 10 digits."), altDeliveryMobile: yup.string(), // .matches( // /^\d{10}$/, // "Alternative mobile number must be exactly 10 digits." // ), customerName: yup.string().required("Please enter the receiver name."), blockNo: allData?.addressFormatType?.name === "Standard" ? yup .object({ id: yup.string().required("Block ID is required."), name: yup.string().optional(), }) .typeError("Please select the block no.") .required("Please select the block no.") : yup.object().optional().nullable(), roadNo: allData?.addressFormatType?.name === "Standard" ? yup .object({ id: yup.string().required("Road ID is required."), name: yup.string().required("Road name is required."), }) .typeError("Please select the road no.") .required("Please select the road no.") : yup.object().optional().nullable(), buildingNo: allData?.addressFormatType?.name === "Standard" ? yup .object({ id: yup.string().required("Building ID is required."), name: yup.string().required("Building name is required."), }) .typeError("Please select the building no.") .required("Please select the building no.") : yup.object().optional().nullable(), flatNo: allData?.addressFormatType?.name === "Standard" ? yup.string().required("Please enter the flat/office no.") : yup.string().optional(), destinationAddress: allData?.addressFormatType?.name === "SingleLine" ? yup.string().required("Please enter receiver address") : yup.string().optional(), remarks: yup.string().optional(), delivery_instructions: yup .string() .max(255, "Delivery instructions must not exceed 255 characters."), }); // Search user by phone number and auto-fill details const searchUserByPhone = async (searchValue: string) => { try { // Clean the search value by removing spaces and formatting const cleanedSearchValue = searchValue.replace(/\s+/g, "").trim(); if (cleanedSearchValue.length >= 3) { const response = await searchCustomerByPhone(cleanedSearchValue); if (response && response.length > 0) { const customer = response[0]; // Get the first matching customer await handleCustomerSelect(customer); } } } catch (error) { console.error("Error searching customer:", error); } }; const handleCustomerSelect = async (customer: any) => { setCustomerName(customer.customer_name); setDeliveryMobile(customer.phone_number); setAltDeliveryMobile(customer.alternative_phone_number || ""); // Set address details if available if (customer.block) { // Find the block option that matches the customer's block ID const blockOption = blocks.find( (b: any) => b.id.toString() === customer.block.toString() ); if (blockOption) { setBlockNo({ value: blockOption.id, label: blockOption.name, id: blockOption.id, code: blockOption.code, name: blockOption.name, }); } else { // If block not found in list, set with ID as name setBlockNo({ value: customer.block.toString(), label: customer.block.toString(), id: customer.block.toString(), name: customer.block.toString(), }); } // Fetch roads for this block if customer has road data if (customer.road) { try { await fetchRoads(customer.block.toString()); // Find the road option that matches the customer's road ID const roadOption = filteredRoads.find( (r: any) => r.id.toString() === customer.road.toString() ); if (roadOption) { setRoadNo({ value: roadOption.id, label: roadOption.name, id: roadOption.id, code: roadOption.code, name: roadOption.name, }); } else { // If road not found in list, try to fetch it by ID try { const roadResponse = await getMasterList("road", { block_id: customer.block.toString(), search: "", }); if (roadResponse?.data?.data) { const allRoads = roadResponse.data.data; const foundRoad = allRoads.find( (r: any) => r.id.toString() === customer.road.toString() ); if (foundRoad) { setRoadNo({ value: foundRoad.id, label: foundRoad.name, id: foundRoad.id, code: foundRoad.code, name: foundRoad.name, }); // Also update the filtered roads list setFilteredRoads( allRoads.map((r: any) => ({ value: r.id, label: r.name, id: r.id, code: r.code, name: r.name, })) ); } else { setRoadNo({ value: customer.road.toString(), label: `Road ${customer.road}`, id: customer.road.toString(), name: `Road ${customer.road}`, }); } } else if (roadResponse?.data) { // Handle case where data is not nested const allRoads = roadResponse.data; const foundRoad = allRoads.find( (r: any) => r.id.toString() === customer.road.toString() ); if (foundRoad) { setRoadNo({ value: foundRoad.id, label: foundRoad.name, id: foundRoad.id, code: foundRoad.code, name: foundRoad.name, }); setFilteredRoads( allRoads.map((r: any) => ({ value: r.id, label: r.name, id: r.id, code: r.code, name: r.name, })) ); } else { setRoadNo({ value: customer.road.toString(), label: `Road ${customer.road}`, id: customer.road.toString(), name: `Road ${customer.road}`, }); } } } catch (roadError) { console.error("Error fetching road by ID:", roadError); setRoadNo({ value: customer.road.toString(), label: `Road ${customer.road}`, id: customer.road.toString(), name: `Road ${customer.road}`, }); } } // If customer also has building data, fetch buildings if (customer.building) { await fetchBuildings( customer.block.toString(), customer.road.toString() ); const buildingOption = filteredBuildings.find( (b: any) => b.id.toString() === customer.building.toString() ); if (buildingOption) { setBuildingNo({ value: buildingOption.id, label: buildingOption.name, id: buildingOption.id, code: buildingOption.code, name: buildingOption.name, }); } else { // If building not found in list, try to fetch it by ID try { const buildingResponse = await getMasterList("building", { block_id: customer.block.toString(), road_id: customer.road.toString(), }); if (buildingResponse?.data) { const allBuildings = buildingResponse.data; const foundBuilding = allBuildings.find( (b: any) => b.id.toString() === customer.building.toString() ); if (foundBuilding) { setBuildingNo({ value: foundBuilding.id, label: foundBuilding.name, id: foundBuilding.id, code: foundBuilding.code, name: foundBuilding.name, }); // Also update the filtered buildings list setFilteredBuildings( allBuildings.map((b: any) => ({ value: b.id, label: b.name, id: b.id, code: b.code, name: b.name, })) ); } else { setBuildingNo({ value: customer.building.toString(), label: `Building ${customer.building}`, id: customer.building.toString(), name: `Building ${customer.building}`, }); } } } catch (buildingError) { console.error("Error fetching building by ID:", buildingError); setBuildingNo({ value: customer.building.toString(), label: `Building ${customer.building}`, id: customer.building.toString(), name: `Building ${customer.building}`, }); } } } } catch (error: unknown) { if (error && typeof error === "object" && "message" in error) { toast.error((error as any).message); } else { toast.error("An error occurred while fetching roads/buildings."); } } } } if (customer.flat_no) { setFlatNo(customer.flat_no); } }; // Function to check if all required fields are filled const checkFormCompletion = React.useCallback( (data: any) => { if (allData?.addressFormatType?.name === "Standard") { // Standard: block/road/building/flat required const blockId = typeof data.blockNo === "object" ? data.blockNo?.id : data.blockNo; const roadId = typeof data.roadNo === "object" ? data.roadNo?.id : data.roadNo; const buildingId = typeof data.buildingNo === "object" ? data.buildingNo?.id : data.buildingNo; return !!( data.deliveryMobile && data.customerName && blockId && roadId && buildingId && data.flatNo ); } else { // Non-standard: only destination_address required return !!( data.deliveryMobile && data.customerName && data.destination_address ); } }, [allData] ); // Function to check if form data has actually changed const hasFormDataChanged = React.useCallback( (currentData: any, previousData: any) => { if (!previousData) return true; return JSON.stringify(currentData) !== JSON.stringify(previousData); }, [] ); // Debounced API call function // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedApiCall = React.useCallback( debounce(async (formData: any) => { if (isApiCallInProgress.current) return; try { setIsLoading(true); isApiCallInProgress.current = true; const response = await createOrder( { ...formData, orderId: createOrderData?.customerInputOrderId || orderId, step: 2, }, 0, createOrderStep3Data, null, completeOrder ); if (response?.status) { const updatedCompleteOrder = { ...completeOrder, step3: { ...createOrderStep3Data, preview: response?.data || {}, }, }; dispatch(saveStepData({ step: "step3", data: updatedCompleteOrder })); } else if ( response?.message?.includes( "draft_orders_customer_input_order_id_unique" ) ) { Swal.fire({ icon: "error", title: "Duplicate Order ID", text: "The Order ID already exists. Please enter a unique Order ID.", confirmButtonText: "OK", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } else { // Swal.fire({ // icon: "error", // title: "Error", // text: response?.message || "", // confirmButtonText: "OK", // customClass: { // confirmButton: "delybell-primary px-4", // cancelButton: "delybell-dark", // }, // }); } } catch (err) { setIsLoading(false); console.error("Order creation failed:", err); } finally { setIsLoading(false); isApiCallInProgress.current = false; } }, 1000), // 1 second debounce [completeOrder, createOrderStep3Data, dispatch] ); const apiCall = useCallback( async (values: any) => { try { setIsLoading(true); isApiCallInProgress.current = true; const response = await createOrder( { ...values, step: 2 }, 0, createOrderStep3Data, null, completeOrder ); if (response?.status) { const updatedCompleteOrder = { ...completeOrder, step3: { ...createOrderStep3Data, preview: response?.data || {}, }, }; dispatch(saveStepData({ step: "step3", data: updatedCompleteOrder })); } else if ( response?.message?.includes( "draft_orders_customer_input_order_id_unique" ) ) { Swal.fire({ icon: "error", title: "Duplicate Order ID", text: "The Order ID already exists. Please enter a unique Order ID.", confirmButtonText: "OK", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } } catch (err) { setIsLoading(false); console.error("Order creation failed:", err); } finally { setIsLoading(false); isApiCallInProgress.current = false; } }, [completeOrder, createOrderStep3Data, dispatch] ); // Effect to handle form completion and API calls React.useEffect(() => { if (createOrderStep3Data?.step1?.id) { dispatch(setReduxOrderId(createOrderStep3Data.step1.id)); } const formData = allData?.addressFormatType?.name === "Standard" ? { deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, sender_address: senderAddress, destination_address: destinationAddress, // ...other standard fields } : { deliveryMobile, altDeliveryMobile, customerName, destination_address: destinationAddress, remarks, // ...other non-standard fields }; const isComplete = checkFormCompletion(formData); setIsFormComplete(isComplete); // Only make API call if: // 1. Form is complete // 2. Form data has actually changed // 3. No API call is currently in progress if ( isComplete && // hasFormDataChanged(formData, previousFormData.current) && !isApiCallInProgress.current ) { previousFormData.current = formData; debouncedApiCall(formData); } // Cleanup function to cancel any pending debounced calls return () => { debouncedApiCall.cancel(); }; }, [ deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, senderAddress, destinationAddress, createOrderStep3Data, completeOrder, checkFormCompletion, hasFormDataChanged, debouncedApiCall, dispatch, allData, ]); // For first time React.useEffect(() => { if (createOrderStep3Data?.step1?.id) { dispatch(setReduxOrderId(createOrderStep3Data.step1.id)); } const formData = allData?.addressFormatType?.name === "Standard" ? { deliveryMobile, altDeliveryMobile, customerName, destination_address: destinationAddress, remarks, // ...other standard fields } : { deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, sender_address: senderAddress, destination_address: destinationAddress, // ...other non-standard fields }; const isComplete = checkFormCompletion(formData); setIsFormComplete(isComplete); // Only make API call if: // 1. Form is complete // 2. It's the first time // 3. No API call is currently in progress if ( isComplete && !isFirstApiCallMade.current && !isApiCallInProgress.current ) { isFirstApiCallMade.current = true; previousFormData.current = formData; apiCall(formData); } // Cleanup function to cancel any pending debounced calls return () => { debouncedApiCall.cancel(); }; }, [ deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, senderAddress, destinationAddress, createOrderStep3Data, completeOrder, checkFormCompletion, dispatch, apiCall, debouncedApiCall, allData, ]); // Function to fetch blocks const fetchBlocks = async () => { try { setIsBlockLoading(true); const response = await getMasterList("block"); if (response?.data) { setBlocks( response.data.map((b: any) => ({ value: b.id, label: b.name, id: b.id, code: b.code, name: b.name, })) ); } } catch (err) { console.error("Error loading blocks:", err); } finally { setIsBlockLoading(false); } }; // Function to fetch roads based on block const fetchRoads = async (blockId: string) => { try { if (!blockId) return; else { setIsRoadLoading(true); const response = await getMasterList("road", { block_id: blockId, search: "", }); if (response?.data?.data) { // Check if response has nested data structure const roadsData = response.data.data; setFilteredRoads( roadsData.map((r: any) => ({ value: r.id, label: r.name, id: r.id, code: r.code, name: r.name, })) ); } else if (response?.data) { // Handle direct data structure setFilteredRoads( response.data.map((r: any) => ({ value: r.id, label: r.name, id: r.id, code: r.code, name: r.name, })) ); } else { setFilteredRoads([]); } } } catch (err) { console.error("Error loading roads:", err); setFilteredRoads([]); } finally { setIsRoadLoading(false); } }; // Function to fetch buildings based on block and road const fetchBuildings = async (blockId: string, roadId: string) => { try { if (!blockId || !roadId) return; else { setIsBuildingLoading(true); const response = await getMasterList("building", { block_id: blockId, road_id: roadId, }); if (response?.data) { setFilteredBuildings( response.data.map((b: any) => ({ value: b.id, label: b.name, id: b.id, code: b.code, name: b.name, })) ); } } } catch (err) { console.error("Error loading buildings:", err); } finally { setIsBuildingLoading(false); } }; // Effect to handle initial data population and fetch blocks React.useEffect(() => { // Fetch blocks on component mount fetchBlocks(); if (createOrderData || defaultOrderDetails) { const isReturnFlow = completeOrder?.step1?.orderFlowType?.id === OrderFlowType?.Return || packageDisable === "true"; const blockDetails = isReturnFlow ? defaultOrderDetails?.pickup_block_details : defaultOrderDetails?.destination_block_details; const roadDetails = isReturnFlow ? defaultOrderDetails?.pickup_road_details : defaultOrderDetails?.destination_road_details; const buildingDetails = isReturnFlow ? defaultOrderDetails?.pickup_building_details : defaultOrderDetails?.destination_building_details; const flatOrOfficeNumber = isReturnFlow ? defaultOrderDetails?.pickupFlatOrOfficeNumber : defaultOrderDetails?.destinationFlatOrOfficeNumber; // const customerName = isReturnFlow // ? defaultOrderDetails?.pickupCustomerName // : defaultOrderDetails?.destinationCustomerName; // const mobileNumber = isReturnFlow // ? defaultOrderDetails?.pickupMobileNumber // : defaultOrderDetails?.destinationMobileNumber; // const altMobileNumber = isReturnFlow // ? defaultOrderDetails?.pickupAlternateNumber // : defaultOrderDetails?.destinationAlternateNumber; const initialData = { deliveryMobile: createOrderData?.deliveryMobile || defaultOrderDetails?.destinationMobileNumber || "", altDeliveryMobile: createOrderData?.altDeliveryMobile || defaultOrderDetails?.destinationAlternateNumber || "", customerName: createOrderData?.customerName || defaultOrderDetails?.destinationCustomerName || "", blockNo: // createOrderData?.blockNo || blockDetails?.id ? { value: blockDetails?.id, label: blockDetails?.name, id: blockDetails?.id, code: blockDetails?.code, name: blockDetails?.name, } : null, roadNo: // createOrderData?.roadNo || roadDetails?.id ? { value: roadDetails?.id, label: roadDetails?.name, id: roadDetails?.id, code: roadDetails?.code, name: roadDetails?.name, } : null, buildingNo: // createOrderData?.buildingNo || buildingDetails?.id ? { value: buildingDetails?.id, label: buildingDetails?.name, id: buildingDetails?.id, code: buildingDetails?.code, name: buildingDetails?.name, } : null, flatNo: createOrderData?.flatNo || flatOrOfficeNumber || "", remarks: createOrderData?.remarks || defaultOrderDetails?.deliveryInstructions || "", sender_address: createOrderData?.sender_address || "", destination_address: createOrderData?.destination_address || "", customerInputOrderId: createOrderData?.customerInputOrderId, }; // Set all form fields setOrderId(initialData?.customerInputOrderId || ""); setDeliveryMobile(initialData.deliveryMobile); setAltDeliveryMobile(initialData.altDeliveryMobile); setCustomerName(initialData.customerName); setBlockNo(initialData.blockNo); setRoadNo(initialData.roadNo); setBuildingNo(initialData.buildingNo); setFlatNo(initialData.flatNo); setRemarks(initialData.remarks); setSenderAddress(initialData.sender_address); setDestinationAddress( initialData.destination_address || initialData.sender_address ); setSenderAddressChars(255 - (initialData.sender_address?.length || 0)); setDestinationAddressChars( 255 - (initialData.destination_address?.length || 0) ); // Set previous form data to trigger API call previousFormData.current = initialData; } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Empty dependency array to run only once on mount const handlePhoneChange = (value: string, isAlternative: boolean = false) => { if (isAlternative) { setAltDeliveryMobile(value || ""); onSave({ altDeliveryMobile: value || "" }); } else { setDeliveryMobile(value || ""); onSave({ deliveryMobile: value || "" }); } // Validate contact number using new helper (default to 'BH') if (value) { const result = validatePhoneNumberByCountry( value, selectedCountry || "BH" ); if (!result.isValid) { setErrors((prev) => ({ ...prev, [isAlternative ? "altDeliveryMobile" : "deliveryMobile"]: result.error, })); } else { setErrors((prev) => ({ ...prev, [isAlternative ? "altDeliveryMobile" : "deliveryMobile"]: undefined, })); } } else if (!isAlternative) { setErrors((prev) => ({ ...prev, deliveryMobile: "Contact number is required", })); } }; // Update validateField function const validateField = async (fieldName: string, value: any) => { try { if (fieldName === "deliveryMobile" || fieldName === "altDeliveryMobile") { if (!value) { if (fieldName === "deliveryMobile") { setErrors((prev) => ({ ...prev, [fieldName]: "Contact number is required", })); return false; } return true; // Alternative number is optional } // Use new helper (default to 'BH') const result = validatePhoneNumberByCountry( value.toString(), selectedCountry || "BH" ); if (!result.isValid) { setErrors((prev) => ({ ...prev, [fieldName]: result.error, })); return false; } setErrors((prev) => ({ ...prev, [fieldName]: undefined })); return true; } await step2Schema.validateAt(fieldName, { [fieldName]: value }); setErrors((prev) => ({ ...prev, [fieldName]: undefined })); return true; } catch (err: any) { setErrors((prev) => ({ ...prev, [fieldName]: err.message })); return false; } }; const handleInputChange = (setter: React.Dispatch>, fieldName: string) => async (e: React.ChangeEvent) => { const newValue = e.target.value; setter(newValue); onSave({ [fieldName]: newValue }); await validateField(fieldName, newValue); }; const handleTextareaChange = async ( e: React.ChangeEvent ) => { const newValue = e.target.value; setRemarks(newValue); setRemainingChars(255 - newValue.length); onSave({ remarks: newValue }); await validateField("remarks", newValue); }; const validateStep2 = async () => { try { // Validate the data against the schema await step2Schema.validate( { orderId, deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, sender_address: senderAddress, destination_address: destinationAddress, destinationAddress, }, { abortEarly: false } ); setErrors({}); // If validation passes, make the API call setIsLoading(true); const formData = allData?.addressFormatType?.name === "Standard" ? { deliveryMobile, altDeliveryMobile, customerName, blockNo, roadNo, buildingNo, flatNo, remarks, sender_address: senderAddress, destination_address: destinationAddress, // ...other standard fields } : { deliveryMobile, altDeliveryMobile, customerName, destination_address: destinationAddress, remarks, // ...other non-standard fields }; const response = await createOrder( { ...formData, orderId: createOrderData?.customerInputOrderId || orderId, step: 2, }, 0, createOrderStep3Data, null, completeOrder ); setIsLoading(false); if (response?.status) { const updatedCompleteOrder = { ...completeOrder, step3: { ...createOrderStep3Data, preview: response?.data || {}, }, }; dispatch(saveStepData({ step: "step3", data: updatedCompleteOrder })); return true; } else { if ( response?.message?.includes( "draft_orders_customer_input_order_id_unique" ) ) { Swal.fire({ icon: "error", title: "Duplicate Order ID", text: "The Order ID already exists. Please enter a unique Order ID.", confirmButtonText: "OK", customClass: { confirmButton: "delybell-primary px-4", cancelButton: "delybell-dark", }, }); } else { toast.error(response?.message || "Failed to validate step 2"); } return false; } } catch (err: any) { setIsLoading(false); // Create a new error object to capture each field error const newErrors: Record = {}; if (err.inner) { // Iterate over each validation error err.inner.forEach((e: any) => { newErrors[e.path] = e.message; }); } else { console.error("Validation error:", err); } // Set the errors state setErrors(newErrors); return false; } }; const handleBlockInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setNewBlock((prev) => ({ ...prev, [name]: value })); }; const handleRoadInputChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setNewRoad((prev) => ({ ...prev, [name]: value })); }; const handleBuildingInputChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setNewBuilding((prev) => ({ ...prev, [name]: value })); }; const handleBlockSave = async () => { if (!newBlock.code || !newBlock.name) return; try { const response = await createBlock(newBlock); if (response && response.status) { const newOption: OptionType = { value: response.data.id, label: response.data.name, id: response.data.id, code: response.data.code, name: response.data.name, }; setBlocks([...blocks, newOption]); setBlockNo(newOption); setIsBlockModalOpen(false); setNewBlock({ code: "", name: "", name_ar: "" }); toast.success("Block added successfully!"); setErrors({ ...errors, blockNo: "" }); } else { // Handle API error response const errorMessage = response?.message || "Failed to add block. Please try again."; toast.error(errorMessage); } } catch (error: any) { console.error("Error adding block:", error); const errorMessage = error?.response?.data?.message || error?.message || "Failed to add block. Please try again."; toast.error(errorMessage); } finally { validateStep2(); } }; const handleRoadSave = async (roadData: { code: string; name: string; name_ar: string; block_id: any; }) => { if (!roadData.code || !roadData.name || !roadData.block_id) return; try { const response = await createRoad(roadData); if (response && response.status) { const newRoadObj = { value: response.data.id, label: response.data.name, id: response.data.id, code: response.data.code, name: response.data.name, }; setFilteredRoads([...filteredRoads, newRoadObj]); setRoadNo(newRoadObj); setErrors((prev) => ({ ...prev, roadNo: undefined })); // clear error validateStep2(); // trigger validation toast.success("Road added successfully!"); } else { // Handle API error response const errorMessage = response?.message || "Failed to add road. Please try again."; toast.error(errorMessage); } } catch (error: any) { console.error("Error adding road:", error); const errorMessage = error?.response?.data?.message || error?.message || "Failed to add road. Please try again."; toast.error(errorMessage); } finally { // await validateStep2() } }; const handleBuildingSave = async (buildingData: { code: string; name: string; name_ar: string; block_id: any; road_id: any; }) => { if ( !buildingData.code || !buildingData.name || !buildingData.block_id || !buildingData.road_id ) return; try { const response = await createBuilding(buildingData); if (response && response.status) { const newBuildingObj = { value: response.data.id, label: response.data.name, id: response.data.id, code: response.data.code, name: response.data.name, }; setFilteredBuildings([...filteredBuildings, newBuildingObj]); setBuildingNo(newBuildingObj); setErrors((prev) => ({ ...prev, buildingNo: undefined })); // clear error // validateStep2(); // trigger validation toast.success("Building added successfully!"); } else { // Handle API error response const errorMessage = response?.message || "Failed to add building. Please try again."; toast.error(errorMessage); } } catch (error: any) { console.error("Error adding building:", error); const errorMessage = error?.response?.data?.message || error?.message || "Failed to add building. Please try again."; toast.error(errorMessage); } finally { // validateStep2() } }; // Add options for block, road, building // const blockOptions = locationModeInput().block // ? blocks // : [{ id: "add-new", name: "", code: "" }, ...blocks]; // const roadOptions = locationModeInput().road // ? filteredRoads // : [{ id: "add-new", name: "", code: "" }, ...filteredRoads]; // const buildingOptions = locationModeInput().building // ? filteredBuildings // : [{ id: "add-new", name: "", code: "" }, ...filteredBuildings]; console.log(blockNo, "blockNo"); return ( <> {/* {isLoading && } */}
{/* Order ID Field */}
{errors.orderId && (
{errors.orderId}
)}
{/* Delivery Mobile Field */}
{/* */} { handlePhoneChange(value, false); // Search for existing user when phone number or name is entered if (value && value.length >= 3) { // Clean the value before searching const cleanedValue = value.replace(/\s+/g, "").trim(); searchUserByPhone(cleanedValue); } }} placeholder="Receiver Contact Number or Name" error={errors.deliveryMobile} className="phone-input-country" isFormik={false} country={selectedCountry} onCountryChange={setSelectedCountry} />
{/* {errors.deliveryMobile && (
{errors.deliveryMobile}
)} */}
{/* Customer Name Field */}
{errors.customerName && (
{errors.customerName}
)}
{/* Alternative Mobile Field */}
{/* */} { handlePhoneChange(value, true); }} placeholder={`${ completeOrder?.step1?.orderFlowType?.id === OrderFlowType?.Return || packageDisable === "true" ? "Sender " : "Receiver " } Alt. Contact Number`} error={errors.altDeliveryMobile} className="phone-input-country" isFormik={false} country={selectedCountry} onCountryChange={setSelectedCountry} />
{/* {errors.altDeliveryMobile && (
{errors.altDeliveryMobile}
)} */}
{/* Address Details Section */}
{completeOrder?.step1?.orderFlowType?.id === OrderFlowType?.Return || packageDisable === "true" ? "Pickup " : "Delivery "}{" "} Address Details
{allData?.addressFormatType?.name === "Standard" ? ( <> {/* Block No Field */}
{ const option = val as OptionType | null; setBlockNo(option); setErrors((prev) => ({ ...prev, blockNo: undefined, })); // When block changes, reset downstream fields and fetch new roads. setRoadNo(null); setBuildingNo(null); setFilteredRoads([]); setFilteredBuildings([]); if (option?.id) { fetchRoads(option.id as string); } }} // onAddNew={(inputValue) => { // setNewBlock({ code: inputValue, name: "", name_ar: "" }); // setIsBlockModalOpen(true); // }} skillValue={null} initValue={blocks.map((b) => b.label)} // isDisabled={isBlockLoading} isDisabled={false} placeholder="Select Block" value={blockNo} apiUrl="/user/master/block/list" addNew={false} />
{errors.blockNo && (
{errors.blockNo}
)}
{/* Road No Field */}
{ const option = val as OptionType | null; setRoadNo(option); setErrors((prev) => ({ ...prev, roadNo: undefined })); // When road changes, reset downstream fields and fetch new buildings. setBuildingNo(null); setFilteredBuildings([]); if (option?.id && blockNo?.id) { fetchBuildings( blockNo.id as string, option.id as string ); } }} // onAddNew={(inputValue) => { // handleRoadSave({ // code: String(inputValue), // name: String(inputValue), // name_ar: "", // block_id: String(blockNo?.id || ""), // }); // // setIsRoadModalOpen(true); // }} skillValue={null} optionsData={filteredRoads} // isDisabled={!blockNo || isRoadLoading} isDisabled={false} placeholder="Select Road" stringAdd={true} />
{errors.roadNo && (
{errors.roadNo}
)}
{/* Building No Field */}
{ setBuildingNo(val as OptionType); setErrors((prev) => ({ ...prev, buildingNo: undefined, })); }} // onAddNew={(inputValue) => { // handleBuildingSave({ // code: inputValue, // name: inputValue, // name_ar: "", // block_id: blockNo?.id || "", // road_id: roadNo?.id, // }); // setNewBuilding({ code: inputValue, name: "", name_ar: "", block_id: blockNo?.id || "", road_id: roadNo?.id || "" }); // setIsBuildingModalOpen(true); // }} skillValue={null} optionsData={filteredBuildings} // isDisabled={!blockNo || !roadNo || isBuildingLoading} isDisabled={false} placeholder="Select Building" stringAdd={true} />
{errors.buildingNo && (
{errors.buildingNo}
)}
{/* Flat No Field */}
{errors.flatNo && (
{errors.flatNo}
)}
) : ( <> {/* Destination Address Field (non-mandatory, textarea) */}