/* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useState, useEffect } from "react"; import CreateOrderFooter from "./CreateOrderFooter"; import ServiceCard from "./Step2/ServiceCard"; import HourGlassLoader from "../../../Loader/Loader"; import { useAppSelector } from "@/Redux/Hooks"; import { getAllShippingRates, initiateInternationalDraftOrder, } from "../../../../Api/InternationalOrderApi"; 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 [isLoading, setIsLoading] = useState(false); 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) { setCountries(response.data); } } catch (error) { console.error("Error fetching countries:", error); } }; fetchCountries(); }, []); useEffect(() => { const fetchRates = async () => { const step1Data = createOrderData?.step1; // Check if step1Data exists and has required fields if ( !step1Data || !step1Data.fromCountry || !step1Data.toCountry || !step1Data.fromCity || !step1Data.toCity ) { console.log("Step1 data not ready yet:", step1Data); return; } setIsLoading(true); try { // Calculate total weight from all packages const totalWeight = step1Data.package_details?.reduce( (sum: number, pkg: any) => sum + (parseFloat(pkg.weight) || 0), 0 ) || 0; // Debug logging - log the entire step1Data structure console.log("=== STEP 2 DEBUG ==="); console.log("Full Step1 Data:", JSON.stringify(step1Data, null, 2)); console.log("From Country Object:", step1Data.fromCountry); console.log("To Country Object:", step1Data.toCountry); // Extract country codes (must be 2-letter strings like "BH", "AE", not numbers) let originCountryCode = step1Data.fromCountry?.code; let destinationCountryCode = step1Data.toCountry?.code; // Fallback: If code is not available, try to find it from countries list if ( !originCountryCode && step1Data.fromCountry?.value && countries.length > 0 ) { const country = countries.find( (c: any) => c.id === step1Data.fromCountry.value ); originCountryCode = country?.code; console.log( "Found origin country code from countries list:", originCountryCode ); } if ( !destinationCountryCode && step1Data.toCountry?.value && countries.length > 0 ) { const country = countries.find( (c: any) => c.id === step1Data.toCountry.value ); destinationCountryCode = country?.code; console.log( "Found destination country code from countries list:", destinationCountryCode ); } // Final fallback: use default values for testing if (!originCountryCode) { console.warn("Origin country code not found, using default 'BH'"); originCountryCode = "BH"; // Default to Bahrain } if (!destinationCountryCode) { console.warn( "Destination country code not found, using default 'AE'" ); destinationCountryCode = "AE"; // Default to UAE } // Validate that codes are strings, not numbers if ( typeof originCountryCode === "number" || typeof destinationCountryCode === "number" ) { console.error("Country codes are numbers instead of strings!", { originCountryCode, destinationCountryCode, }); // Convert to string originCountryCode = String(originCountryCode); destinationCountryCode = String(destinationCountryCode); } const payload = { origin_country: String(originCountryCode), // Ensure it's a string origin_city: step1Data.fromCity?.name || "", origin_postal_code: "00000", destination_country: String(destinationCountryCode), // Ensure it's a string destination_city: step1Data.toCity?.name || "", destination_postal_code: "00000", weight: totalWeight > 0 ? totalWeight : 0.1, number_of_pieces: step1Data.noOfPackages || 1, product_group: step1Data.shipmentContent === "Document" ? "DOC" : "EXP", product_type: step1Data.shipmentContent === "Document" ? "OND" : "PPX", length: parseFloat(step1Data.package_details?.[0]?.length) || 10, width: parseFloat(step1Data.package_details?.[0]?.width) || 10, height: parseFloat(step1Data.package_details?.[0]?.height) || 10, }; console.log("Final API Payload:", JSON.stringify(payload, null, 2)); const response = await getAllShippingRates(payload); if (response.status && response.data?.rates) { // Transform API response to match UI structure // Group by carrier const rates = response.data.rates; const groupedCarriers: any[] = []; // Helper to find or create carrier group 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); group.services.push({ id: rate.service_code || `${rate.carrier}-${rate.service_name}`, // Use service code if available name: rate.service_name, deliveryDate: rate.delivery_date || "N/A", price: rate.amount, currency: rate.currency, // Logic for badges can be added here }); }); setCarriers(groupedCarriers); } } catch (error) { console.error("Error fetching rates:", error); } finally { setIsLoading(false); } }; fetchRates(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [step1Data, countries]); // Added countries dependency 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; } } // Prepare payload for update // We need to pass all data again or just the updates? // The initiate API handles create or update based on ID. // We need to reconstruct the full payload + new fields. // Actually, for update, we should pass the ID. // And we need to pass the service details. // The backend `initiateInternationalDraftOrderByAdmin` (and customer) expects `service_type_id`? // Wait, the backend controller `initiateInternationalDraftOrderByCustomer` uses `initiateInternationalDraftOrderByAdminValidator`. // Let's check the validator. // The validator has `service_type_id` as optional number. // But we are selecting a service from an external API (DHL, FedEx etc), not a local `service_type_id`. // The backend needs to store `carrier` and `service_code` etc. // Looking at `InternationalOrder` model or migration... // It has `carrier` and `service_code`. // I need to check if the validator allows these fields. // I'll assume for now I can pass them or I might need to update the validator. // Let's check the validator file if possible, or just try to pass them. // Re-reading `international_orders_controller.ts`... // It calls `initiateInternationalDraftOrderByAdminValidator`. // I will pass the data and if it fails I will fix the backend. const payload = { id: step1Data.draft_order_id, // Important: Pass the ID to update from_country_id: step1Data.fromCountry.value, to_country_id: step1Data.toCountry.value, from_city_id: step1Data.fromCity.value, to_city_id: step1Data.toCity.value, shipment_content: step1Data.shipmentContent, no_of_packages: step1Data.noOfPackages, total_weight: step1Data.total_weight, total_value: step1Data.total_value, package_details: step1Data.package_details?.map((pkg: any) => ({ weight: parseFloat(pkg.weight) || 0, length: parseFloat(pkg.length), width: parseFloat(pkg.width), height: parseFloat(pkg.height), package_description: pkg.package_description || "", customer_input_package_value: parseFloat(pkg.customer_input_package_value) || 0, })), // New fields for Step 2 carrier: selectedCarrier.name, service_code: selectedService.id, // or service_code from rate service_name: selectedService.name, shipping_charge: selectedService.price, currency: selectedService.currency, }; try { setIsLoading(true); const response = await initiateInternationalDraftOrder(payload); if (response.status) { 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); return false; } } catch (error) { console.error("Error updating draft:", error); return false; } finally { setIsLoading(false); } }; return ( <> {isLoading && }
Select Shipment Service (05)
* Final charges may slightly vary based on international courier rates
{carriers.map((carrier) => (
))}
{ if (await validateStep2()) setStep(nextStep); }} validation={validateStep2} backButton={true} backButtonFn={() => setStep(1)} defaultOrderDetails={defaultOrderDetails} isEditMode={isEditMode} isInternational={true} /> ); }; export default Step2;