/* eslint-disable @next/next/no-img-element */ /* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useState, useEffect } from "react"; import CreateOrderFooter from "./CreateOrderFooter"; import ServiceCard from "./Step2/ServiceCard"; import ServiceCardSkeleton from "./Step2/ServiceCardSkeleton"; import { useAppSelector } from "@/Redux/Hooks"; import { getAllShippingRates, initiateInternationalDraftOrder, } from "../../../../Api/InternationalOrderApi"; import { useCurrency } from "@/context/CurrencyContext"; type Step2Props = { onSave: (data: any) => void; step: number; setStep: React.Dispatch>; defaultOrderDetails: any; isEditMode?: boolean; }; const Step2: React.FC = ({ onSave, step, setStep, defaultOrderDetails, isEditMode, }) => { const { currencyName } = useCurrency(); const [isLoading, setIsLoading] = useState(true); const [selectedServiceId, setSelectedServiceId] = useState( null, ); const [carriers, setCarriers] = useState([]); const [countries, setCountries] = useState([]); const createOrderData: any = useAppSelector((state) => state.createOrder); const step1Data = createOrderData?.step1; console.log(step1Data, "step1Data"); // Fetch countries list to get country codes if needed useEffect(() => { const fetchCountries = async () => { try { const { getMasterList } = await import("../../../../Api/MastersApi"); const response = await getMasterList("country"); if (response?.status) { // normalize shape to match OptionType used elsewhere and normalize codes to uppercase strings const countryOptions = response.data.map((c: any) => ({ value: c.id, label: c.name, // normalize code to uppercase string (guard null/undefined) code: String(c.code ?? c.shortCode ?? "").toUpperCase(), name: c.name, id: c.id, })); setCountries(countryOptions); } } catch (error) { console.error("Error fetching countries:", error); } }; fetchCountries(); }, []); // Extracted fetchRates so we can call it from event handler too const fetchRates = async () => { setIsLoading(true); try { // Check if minimal step1 data exists if (!createOrderData?.step1) { console.log( "Step1 data not ready yet for rates:", createOrderData?.step1, ); return; } // require at least country + city info to fetch rates reliably if ( !createOrderData.step1.fromCountry || !createOrderData.step1.toCountry || !createOrderData.step1.fromCity || !createOrderData.step1.toCity ) { console.warn( "Insufficient Step1 location data for rates. fromCountry/toCountry/fromCity/toCity required.", createOrderData.step1, ); return; } const step1 = createOrderData?.step1; // Calculate total weight from all packages const totalWeight = step1.package_details?.reduce( (sum: number, pkg: any) => sum + (parseFloat(pkg.weight) || 0), 0, ) || 0; // Resolve origin/destination country codes robustly: const resolveCountryCodeFromRef = (ref: any) => { if (!ref) return null; // preferred code field if (ref.code && typeof ref.code === "string") return ref.code; // sometimes object has shortCode / shortcode if (ref.shortCode && typeof ref.shortCode === "string") return ref.shortCode; // if ref.value is an id, try lookup from fetched countries list const id = ref.value ?? ref.id ?? null; if (id && countries.length > 0) { const found = countries.find( (c: any) => c.id === id || c.value === id, ); if (found) return found.code ?? found.shortCode ?? null; } // try alternative fields on step1 (snake_case) if (ref.country_code && typeof ref.country_code === "string") return ref.country_code; return null; }; let originCountryCode = resolveCountryCodeFromRef(step1.fromCountry); let destinationCountryCode = resolveCountryCodeFromRef(step1.toCountry); // If codes still missing, don't fetch rates with wrong defaults — abort and warn. if (!originCountryCode || !destinationCountryCode) { console.warn( "Unable to resolve origin/destination country codes for rates. Aborting fetchRates.", { originCountryCode, destinationCountryCode, step1 }, ); return; } // ensure they are strings originCountryCode = String(originCountryCode); destinationCountryCode = String(destinationCountryCode); const payload = { origin_country: originCountryCode, origin_city: step1.fromCity?.name || "", origin_postal_code: "", destination_country: destinationCountryCode, destination_city: step1.toCity?.name || "", destination_postal_code: "", weight: totalWeight > 0 ? totalWeight : 0.1, number_of_pieces: step1.noOfPackages || 1, product_group: step1.shipmentContent === "Document" ? "DOC" : "EXP", product_type: step1.shipmentContent === "Document" ? "OND" : "PPX", length: parseFloat(step1.package_details?.[0]?.length) || 10, width: parseFloat(step1.package_details?.[0]?.width) || 10, height: parseFloat(step1.package_details?.[0]?.height) || 10, }; const response = await getAllShippingRates(payload); if (response.status && response.data?.rates) { const rates = response.data.rates; // Find the cheapest rate to add a badge const cheapestAmount = Math.min(...rates.map((r: any) => r.amount)); const groupedCarriers: any[] = []; const getCarrierGroup = (carrierName: string) => { let group = groupedCarriers.find( (c) => c.id === carrierName.toLowerCase(), ); if (!group) { group = { id: carrierName.toLowerCase(), name: carrierName, logo: getCarrierLogo(carrierName), color: getCarrierColor(carrierName), services: [], }; groupedCarriers.push(group); } return group; }; rates.forEach((rate: any) => { const group = getCarrierGroup(rate.carrier); // Format delivery date/days let deliveryInfo = rate.delivery_date || "N/A"; if (!rate.delivery_date && rate.delivery_days > 0) { deliveryInfo = `${rate.delivery_days} ${rate.delivery_days === 1 ? "Day" : "Days"}`; } else if (!rate.delivery_date && rate.delivery_days === 0) { deliveryInfo = "Standard Delivery"; } group.services.push({ id: rate.service_code || `${rate.carrier}-${rate.service_name}`, name: rate.service_name, deliveryDate: deliveryInfo, price: rate.amount, currency: rate.currency, badge: rate.amount === cheapestAmount ? "Cheapest" : undefined, details: rate.details, // Store full details for later use if needed }); }); setCarriers(groupedCarriers); } } catch (error) { console.error("Error fetching rates:", error); } finally { setIsLoading(false); } }; // run fetchRates when step1Data or countries update useEffect(() => { fetchRates(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [createOrderData?.step1, countries]); // restore selected service after carriers load if Redux has saved selection useEffect(() => { if (carriers.length > 0 && createOrderData?.step2?.selectedServiceId) { setSelectedServiceId(createOrderData.step2.selectedServiceId); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [carriers]); // listen for navigation events so parent components can request reload/restore useEffect(() => { const handler = (e: any) => { try { const detail = e?.detail || {}; if (detail?.step === 2) { // If reload requested, re-run rate fetch and restore selection if (detail?.reload) { fetchRates().then(() => { if (createOrderData?.step2?.selectedServiceId) { setSelectedServiceId(createOrderData.step2.selectedServiceId); } }); } else { // just restore selection if carriers already loaded if (createOrderData?.step2?.selectedServiceId) { setSelectedServiceId(createOrderData.step2.selectedServiceId); } } } } catch (err) { console.error("Navigation event handler error:", err); } }; window.addEventListener("navigate-to-step", handler); return () => window.removeEventListener("navigate-to-step", handler); // eslint-disable-next-line react-hooks/exhaustive-deps }, [createOrderData?.step2, carriers]); const getCarrierLogo = (name: string) => { if (name.toLowerCase().includes("dhl")) return "/images/international/dhl-express-logo.png"; if (name.toLowerCase().includes("fedex")) return "/images/international/fedex-logo.png"; if (name.toLowerCase().includes("aramex")) return "/images/international/aramex-logo.png"; if (name.toLowerCase().includes("smsa")) return "/images/international/smsa.jpg"; return ""; }; const getCarrierColor = (name: string) => { if (name.toLowerCase().includes("dhl")) return "#FFCC00"; if (name.toLowerCase().includes("fedex")) return "#4D148C"; if (name.toLowerCase().includes("aramex")) return "#EF3340"; if (name.toLowerCase().includes("smsa")) return "#F37021"; return "#000"; }; const handleServiceSelect = (serviceId: string) => { setSelectedServiceId(serviceId); }; const validateStep2 = async () => { if (!selectedServiceId) { // toast.error("Please select a shipment service"); return false; } let selectedService: any = null; let selectedCarrier: any = null; for (const carrier of carriers) { const service = carrier.services.find( (s: any) => s.id === selectedServiceId, ); if (service) { selectedService = service; selectedCarrier = carrier; break; } } // Compute robust draft id fallback const draftId = step1Data?.draft_order_id ?? step1Data?.draftOrderId ?? step1Data?.draft_order?.id ?? step1Data?.id ?? null; if (!draftId) { console.warn("Draft order id not found on step1Data:", step1Data); // still proceed but backend may reject - warn for debugging } else { console.log("Using draft id:", draftId); } // Compute total weight from packages if available (same logic as rates fetch) const totalWeight = step1Data?.package_details?.reduce( (sum: number, pkg: any) => sum + (parseFloat(pkg.weight) || 0), 0, ) || 0; // Safely determine number of pieces const numberOfPieces = step1Data?.noOfPackages ?? step1Data?.no_of_packages ?? step1Data?.number_of_pieces ?? 1; // Prepare payload with robust fallbacks and include both id and draft_order_id const payload = { // include multiple keys to ensure backend receives the draft identifier id: draftId, draft_order_id: draftId, from_country_id: step1Data?.fromCountry?.value ?? step1Data?.from_country_id, to_country_id: step1Data?.toCountry?.value ?? step1Data?.to_country_id, from_city_id: step1Data?.fromCity?.value ?? step1Data?.from_city_id, to_city_id: step1Data?.toCity?.value ?? step1Data?.to_city_id, shipment_content: step1Data?.shipmentContent ?? step1Data?.shipment_content, no_of_packages: numberOfPieces, total_weight: totalWeight > 0 ? totalWeight : (step1Data?.total_weight ?? 0.1), total_value: step1Data?.total_value ?? 0, package_details: (step1Data?.package_details || []).map((pkg: any) => ({ weight: parseFloat(pkg.weight) || 0, length: parseFloat(pkg.length) || 0, width: parseFloat(pkg.width) || 0, height: parseFloat(pkg.height) || 0, package_description: pkg.package_description || pkg.description || "", customer_input_package_value: parseFloat(pkg.customer_input_package_value) || parseFloat(pkg.package_value) || 0, })), // New fields for Step 2 carrier: selectedCarrier?.name ?? selectedCarrier?.id ?? "", // service_code: selectedService?.id ?? selectedService?.service_code ?? "", service_code: selectedServiceId ?? "", service_name: selectedService?.name ?? "", shipping_charge: selectedService?.price ?? selectedService?.amount ?? 0, currency: selectedService?.currency ?? currencyName, }; console.log("Validate Step2 payload:", JSON.stringify(payload, null, 2)); try { setIsLoading(true); const response = await initiateInternationalDraftOrder(payload); if (response?.status) { // Save selected service info back to parent/store onSave({ selectedServiceId, selectedServiceDetails: selectedService, selectedCarrierDetails: { id: selectedCarrier?.id, name: selectedCarrier?.name, logo: selectedCarrier?.logo, }, }); return true; } else { console.error("Failed to update draft:", response?.message, response); return false; } } catch (error) { console.error("Error updating draft:", error); return false; } finally { setIsLoading(false); } }; return ( <>
Select Shipment Service ( {isLoading ? "..." : carriers .reduce((sum, c) => sum + c.services.length, 0) .toString() .padStart(2, "0")} )
* Final charges may slightly vary based on international courier rates
{isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : carriers.length === 0 ? (
No Services Found

No Shipping Services Found

We couldn't find any carriers for the selected route and package details.
Please try adjusting your shipment details to see available options.

) : (
{carriers.map((carrier) => (
))}
)}
{ if (await validateStep2()) setStep(nextStep); }} validation={validateStep2} backButton={true} backButtonFn={() => { setStep(1); try { window.dispatchEvent( new CustomEvent("navigate-to-step", { detail: { step: 1, reload: true }, }), ); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { /* ignore */ } }} defaultOrderDetails={defaultOrderDetails} isEditMode={isEditMode} isInternational={true} /> ); }; export default Step2;