/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ import React, { useRef, useState, useMemo, forwardRef, useImperativeHandle, useEffect, } from "react"; import { Formik, Field, Form, ErrorMessage, FormikHelpers, FormikProps, } from "formik"; import * as Yup from "yup"; import { Row } from "react-bootstrap"; import HourGlassLoader from "../../../Loader/Loader"; import SelectField from "./SelectField"; import InputFieldCustom from "./InputFieldCustom"; import ShipmentTypeSelector from "./ShipmentTypeSelector"; import ShipmentPreview from "./ShipmentPreview"; import { toast } from "react-toastify"; import { createShipment, getWarehouse, updateShipment } from "../../../../Api/ShipmentApi"; import { modeOfTransportOptions } from "@/Constant/options"; import ModeOfTransportTypeSelector from "./ModeOfTransportTypeSelector"; import { ShipmentStatus } from "@/Constant/enums"; import { useRouter } from "next/navigation"; // not "next/router" import { DefaultShipmentType } from "@/Constant"; // Local option type compatible with SelectField type SelectOptionType = { id: string | number; label: string }; interface ServiceType { id: number; name: string; } export interface Option { id: string; label: string; } export interface ShipmentFormRef { submitForm: () => void; } export interface ShipmentDetailsFormProps { onClose?: () => void; editShipment?: any; step: number; onStepChange?: (step: number) => void; setShipmentDetails: any; } export interface ShipmentFormValuesTypes { [key: string]: any; } // Mock service icon map until import path is resolved const serviceIconMapShipment: Record = { Import: { icon: "arrow-down", color: "color-yellow2" }, Export: { icon: "arrow-up", color: "color-yellow2" }, }; const initialValues: any = { shipped_by: "", received_by: "", // upload_manifest: null, shipment_number: "", vessel_name: "", shipment_date: "", eta_date: "", shipment_address: "", receiver_address: "", mode_of_transport: "", cargo_type: "", remarks: "", status: ShipmentStatus.Created, service_type: "", }; // Validation Schema using Yup const validationSchema = Yup.object().shape({ status: Yup.mixed() .oneOf([ShipmentStatus.Created, ShipmentStatus.Shipped]) .required("Status is required"), service_type: Yup.mixed() .required("Service type is required"), // Always required in both statuses shipped_by: Yup.string().required("Shipped by is required"), received_by: Yup.string().required("Received by is required"), shipment_address: Yup.string() .max(255, "Max 255 characters allowed") .required("Shipment address is required"), receiver_address: Yup.string() .max(255, "Max 255 characters allowed") .required("Receiver address is required"), // Required only when status is Shipped shipment_number: Yup.string().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("Shipment number is required") : schema ), mode_of_transport: Yup.mixed().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("Mode of transport is required") : schema ), cargo_type: Yup.string().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("Cargo type is required") : schema ), vessel_name: Yup.string().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("Vessel name is required") : schema ), shipment_date: Yup.string().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("Shipment date is required") : schema ), eta_date: Yup.string().when("status", (status, schema) => Number(status) === ShipmentStatus.Shipped ? schema.required("ETA date is required") : schema ), // Remarks never mandatory remarks: Yup.string().max(255, "Max 255 characters allowed"), }); const ShipmentDetails = forwardRef( ({ onClose, editShipment, step, onStepChange, setShipmentDetails }, ref) => { const [isLoading, setIsLoading] = useState(false); const router = useRouter(); const serviceTypes: ServiceType[] = [ { id: 1, name: "Import" }, { id: 2, name: "Export" }, ]; const yesNoOptions: Option[] = [ { id: "yes", label: "Yes" }, { id: "no", label: "No" }, ]; const mapEditShipmentToFormValues = (shipment: any) => { if (!shipment) return initialValues; // Resolve service type from available shipment data const matchedServiceTypeLocal = serviceTypes.find((type) => type.name === shipment.type?.name) || null; return { shipped_by: shipment.shippedByCustomerId?.toString() ?? shipment.shippedByWarehouseId?.toString() ?? "", received_by: shipment.receivedByCustomerId?.toString() ?? shipment.receivedByWarehouseId?.toString() ?? "", shipment_number: shipment.shipmentNumber || "", vessel_name: shipment.vesselName || "", shipment_date: shipment.shipmentDate || "", eta_date: shipment.etaDate || "", shipment_address: shipment.shippedByAddress || "", receiver_address: shipment.receivedByAddress || "", mode_of_transport: shipment?.mode?.id || "", cargo_type: shipment.cargoName || "", remarks: shipment.remarks || "", status: shipment.status?.id ?? shipment.status ?? ShipmentStatus.Created, service_type: matchedServiceTypeLocal?.id ?? shipment.type?.id ?? "", }; }; const [selectedServiceType, setSelectedServiceType] = useState(null); const formikRef = useRef>(null); // Get current user data from localStorage const [currentUserData, setCurrentUserData] = useState(null); useImperativeHandle(ref, () => ({ submitForm: () => { formikRef.current?.submitForm(); }, })); // Load current user data from localStorage on component mount React.useEffect(() => { try { const storedUser = localStorage.getItem("ALL_DATA"); const allData = storedUser ? JSON.parse(storedUser)?.data : null; setCurrentUserData(allData); } catch (error) { console.error("Error parsing localStorage data:", error); } }, []); const formInitialValues = useMemo(() => { if (editShipment?.id) { const mappedValues = mapEditShipmentToFormValues(editShipment); // Set selected service type for UI const matchedServiceType = serviceTypes.find((type) => type.name === editShipment.type?.name) || null; setSelectedServiceType(matchedServiceType); return mappedValues; } // For new shipment: use default type setSelectedServiceType(DefaultShipmentType); return { ...initialValues, service_type: DefaultShipmentType.id, }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [editShipment]); useEffect(() => { if (!editShipment?.id && formikRef.current) { formikRef.current.setFieldValue("service_type", DefaultShipmentType.id); handleChange(DefaultShipmentType.name); // apply default auto-fill logic } // eslint-disable-next-line react-hooks/exhaustive-deps }, [editShipment]); // Run default service type logic after form is initialized useEffect(() => { if (!editShipment?.id && formikRef.current && currentUserData?.id) { // Set service type in form formikRef.current.setFieldValue("service_type", DefaultShipmentType.id); // Apply default auto-fill logic (calls handleChange) handleChange(DefaultShipmentType.name); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [editShipment, currentUserData]); // const formInitialValues = useMemo(() => { // return editShipment?.id // ? { ...initialValues, ...editShipment } // : initialValues; // }, [editShipment]); const handleChange = (serviceName: string | null) => { const matched = serviceTypes.find((type) => type.name === serviceName) || null; setSelectedServiceType(matched); // Reset and auto-fill shipped_by and received_by when service type changes if (formikRef.current && currentUserData?.id) { // Keep a hidden field in sync for validation formikRef.current.setFieldValue("service_type", matched?.id ?? ""); if (matched?.name === "Import") { // For Import: received_by = current user, shipped_by = empty (user selects) formikRef.current.setFieldValue("received_by", currentUserData.id.toString()); formikRef.current.setFieldValue("shipped_by", ""); } else if (matched?.name === "Export") { // For Export: shipped_by = current user, received_by = empty (user selects) formikRef.current.setFieldValue("shipped_by", currentUserData.id.toString()); formikRef.current.setFieldValue("received_by", ""); } else { // Reset both fields formikRef.current.setFieldValue("shipped_by", ""); formikRef.current.setFieldValue("received_by", ""); } } }; const [shippedByOptions, setShippedByOptions] = useState([]); useEffect(() => { const fetchOptions = async () => { if (!selectedServiceType) { setShippedByOptions([]); return; } if (selectedServiceType.name === "Import") { // Fetch from API instead of static options try { const warehouseData = await getWarehouse(); // Map warehouse data into your OptionType shape, adjust keys as needed const options = warehouseData.map((item: any) => ({ id: item.id.toString(), label: item.name || item.company_name || `Warehouse ${item.id}`, })); setShippedByOptions(options); } catch (error) { console.error("Error fetching warehouse data:", error); setShippedByOptions([]); } } else if (selectedServiceType.name === "Export") { // Fetch from API instead of static options try { const warehouseData = await getWarehouse(); // Map warehouse data into your OptionType shape, adjust keys as needed const options = warehouseData.map((item: any) => ({ id: item.id.toString(), label: item.name || item.company_name || `Warehouse ${item.id}`, })); setShippedByOptions(options); } catch (error) { console.error("Error fetching warehouse data:", error); setShippedByOptions([]); } } else { setShippedByOptions([]); } }; fetchOptions(); }, [selectedServiceType, currentUserData, editShipment]); const remainingChars = (value: string) => 255 - (value?.length || 0); const handleSubmit = async (values: any, actions: FormikHelpers) => { if (step === 1) { // Validate form and create shipment try { setIsLoading(true); // Extract shipped_by and received_by IDs from form values const shippedById = values.shipped_by; // assuming it's already the id string/number const receivedById = values.received_by; // Prepare shipment data with correct keys const shipmentData = { ...values, type: selectedServiceType?.id ?? values?.service_type ?? null, shipped_by_id: shippedById, received_by_id: receivedById, cargo_name: values?.cargo_type, shipped_by_address: values?.shipment_address, received_by_address: values?.receiver_address, mode: values?.mode_of_transport, status: Number(values?.status), }; // Remove original keys if you want (optional) delete shipmentData.shipped_by; delete shipmentData.received_by; delete shipmentData.service_type; delete shipmentData?.cargo_type delete shipmentData?.shipment_address delete shipmentData?.receiver_address delete shipmentData?.mode_of_transport const response = editShipment?.id ? await updateShipment({...shipmentData, shipment_id: editShipment?.id}): await createShipment(shipmentData); if (response?.status) { // onClose?.(); // setShipmentDetails(response?.data) router.push(`${window.location.pathname}?id=${response?.data?.id}`); if(shipmentData?.status === ShipmentStatus.Shipped) window.location.href = `${window.location.pathname}?id=${response?.data?.id}` toast.success(response?.message, { position: "top-right", autoClose: 3000, }); // Move to step 2 if (onStepChange) { onStepChange(2); } } else { toast.error(response?.message || "Failed to create shipment", { position: "top-right", autoClose: 3000, }); } } catch (error: any) { console.error("Error creating shipment:", error); toast.error("An error occurred while creating the shipment", { position: "top-right", autoClose: 3000, }); } finally { setIsLoading(false); actions.setSubmitting(false); } } else if (step === 2) { // onClose?.(); } }; // const isFormValid = ( // values: any, // fieldName?: string // ): { valid: boolean; field?: string } => { // const requiredFields: Record = { // shipped_by: !!values.shipped_by, // received_by: !!values.received_by, // shipment_address: !!values.shipment_address, // receiver_address: !!values.receiver_address, // }; // if (Number(values.status) === ShipmentStatus.Shipped) { // requiredFields.shipment_number = !!values.shipment_number; // requiredFields.mode_of_transport = !!values.mode_of_transport; // requiredFields.cargo_type = !!values.cargo_type; // requiredFields.vessel_name = !!values.vessel_name; // requiredFields.shipment_date = !!values.shipment_date; // requiredFields.eta_date = !!values.eta_date; // } // if (fieldName) { // return { // valid: requiredFields[fieldName], // field: fieldName, // }; // } // // Whole form check (first invalid field if any) // for (const [field, isValid] of Object.entries(requiredFields)) { // if (!isValid) return { valid: false, field }; // } // return { valid: true }; // }; const isFieldRequired = (fieldName: string, values: any): boolean => { // Always required const alwaysRequired = ["shipped_by", "received_by", "shipment_address", "receiver_address"]; if (alwaysRequired.includes(fieldName)) { return true; } // Required only when status = SHIPPED if (Number(values.status) === ShipmentStatus.Shipped) { const shippedRequired = [ "shipment_number", "mode_of_transport", "cargo_type", "vessel_name", "shipment_date", "eta_date", ]; return shippedRequired.includes(fieldName); } // Otherwise not required return false; }; return ( <> {({ values, errors, touched, setFieldValue }: any) => (
{step === 1 && (
Service Type
{/*
Mode of Transport
*/}
{/* Conditional Shipped By field */} {selectedServiceType?.name !== "Export" ? ( ) : selectedServiceType?.name !== "Import" && ( )} {/* Shipment Address */}
{touched.shipment_address && errors.shipment_address && (
{errors.shipment_address}
)}
{remainingChars(values.shipment_address)} characters remaining
{/* Receiver Address */}
{touched.receiver_address && errors.receiver_address && (
{errors.receiver_address}
)}
{remainingChars(values.receiver_address)} characters remaining
{remainingChars(values.remarks)} characters remaining
)} {step === 2 && ( <> )}
)}
{isLoading && } ); } ); ShipmentDetails.displayName = "ShipmentDetails"; export default ShipmentDetails;