import React, { useEffect, useState, useRef, forwardRef, useImperativeHandle, } from "react"; import { usePage } from "@inertiajs/react"; import axios from "axios"; import { faSquareCheck, faTruck, faXmark, faBarcode, faArrowLeft, faQuestionCircle, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Item, ShipmentRequest } from "@/interfaces/interfaces"; import WarehouseExpediceDialog from "@/Components/WarehouseExpediceDialog"; import PdaView from "@/Pages/PdaView"; // Define an interface for the imperative handle exposed by a row. interface RowHandle { triggerAnimation: () => void; } // ------------------- ParcelRow (for marking as processed) ------------------- interface ParcelRowProps { shipment: ShipmentRequest; onProcess: (shipment: ShipmentRequest) => void; onOpenDialog: (shipment: ShipmentRequest, type: "parcels") => void; } const ParcelRow = forwardRef( ({ shipment, onProcess, onOpenDialog }, ref) => { // "none" | "bounce" | "fade" const [animationPhase, setAnimationPhase] = useState< "none" | "bounce" | "fade" >("none"); // Expose a method to trigger the animation from the parent (dialog) useImperativeHandle(ref, () => ({ triggerAnimation: () => { setAnimationPhase("bounce"); setTimeout(() => { setAnimationPhase("fade"); }, 1000); setTimeout(() => { onProcess(shipment); }, 2000); }, })); const handleRowClick = () => { onOpenDialog(shipment, "parcels"); }; const handleProcess = (e: React.MouseEvent) => { e.stopPropagation(); setAnimationPhase("bounce"); setTimeout(() => { setAnimationPhase("fade"); }, 1000); setTimeout(() => { onProcess(shipment); }, 2000); }; /* Determine classes based on the animation phase: - "none": border-base-300, bg-base-100, text-base-content - "bounce": border-primary, bg-primary, text-primary-content - "fade": border-primary, bg-primary, text-primary-content, opacity-0 */ const containerClasses = ` p-4 border rounded-lg shadow flex flex-col space-y-3 transition-all duration-1000 cursor-pointer ${ animationPhase === "none" ? "border-base-300 bg-base-100 text-base-content opacity-100" : animationPhase === "bounce" ? "border-primary bg-primary text-primary-content opacity-100" : "border-primary bg-primary text-primary-content opacity-0" } `; return (

{shipment.shipment_reference}

{shipment.items.map((item) => (

{item.quantity}× {item.name}

{item.model_number}

{(item.price / item.quantity).toFixed(2)}{" "} {shipment.currency}

{Object.entries(item.stockData).map( ([stockName, stockArray]) => stockArray.map((stock, idx) => (

{stockName}

Sklad: {stock.location}

{stock.count} ks

)) )}
))}
{animationPhase === "bounce" ? ( ) : ( )}
); } ); // ------------------- ProcessedRow (for marking as unprocessed) ------------------- interface ProcessedRowProps { shipment: ShipmentRequest; onUnprocess: (shipment: ShipmentRequest) => void; onOpenDialog: (shipment: ShipmentRequest, type: "processed") => void; } const ProcessedRow = forwardRef( ({ shipment, onUnprocess, onOpenDialog }, ref) => { const [animationPhase, setAnimationPhase] = useState< "none" | "bounce" | "fade" >("none"); useImperativeHandle(ref, () => ({ triggerAnimation: () => { setAnimationPhase("bounce"); setTimeout(() => { setAnimationPhase("fade"); }, 1000); setTimeout(() => { onUnprocess(shipment); }, 2000); }, })); const handleRowClick = () => { onOpenDialog(shipment, "processed"); }; const handleUnprocess = (e: React.MouseEvent) => { e.stopPropagation(); setAnimationPhase("bounce"); setTimeout(() => { setAnimationPhase("fade"); }, 1000); setTimeout(() => { onUnprocess(shipment); }, 2000); }; /* Determine classes based on the animation phase: - "none": border-base-300, bg-base-100, text-base-content - "bounce": border-error, bg-error, text-error-content - "fade": border-error, bg-error, text-error-content, opacity-0 */ const containerClasses = ` p-4 border rounded-lg shadow flex flex-col space-y-3 transition-all duration-1000 cursor-pointer ${ animationPhase === "none" ? "border-base-300 bg-base-100 text-base-content opacity-100" : animationPhase === "bounce" ? "border-error bg-error text-error-content opacity-100" : "border-error bg-error text-error-content opacity-0" } `; return (

{shipment.shipment_reference}

{shipment.items.map((item) => (

{item.quantity}× {item.name}

{item.model_number}

{(item.price / item.quantity).toFixed(2)}{" "} {shipment.currency}

{Object.entries(item.stockData).map( ([stockName, stockArray]) => stockArray.map((stock, idx) => (

{stockName}

Sklad: {stock.location}

{stock.count} ks

)) )}
))}
{animationPhase === "bounce" ? ( ) : ( )}
); } ); type InertiaProps = { auth: any; // adjust per your auth type selectedBatchID: number; }; // ------------------- Main Component ------------------- export default function WarehouseExpedicePage() { const { selectedBatchID } = usePage().props; // States for shipments const [parcels, setParcels] = useState([]); const [parcelsOther, setParcelsOther] = useState([]); const [processed, setProcessed] = useState([]); const [loading, setLoading] = useState(true); // Refs to control individual row animations const parcelRowRefs = useRef<{ [key: number]: RowHandle | null }>({}); const processedRowRefs = useRef<{ [key: number]: RowHandle | null }>({}); // Dialog state const [selectedShipment, setSelectedShipment] = useState(null); const [selectedType, setSelectedType] = useState< "parcels" | "processed" | null >(null); const [isDialogOpen, setIsDialogOpen] = useState(false); const handleCopyToClipboard = () => { const copyString = parcels.map((p) => p.shipment_reference).join(","); const copyStringOther = parcelsOther.map((p) => p.shipment_reference).join( "," ); const copyStringProcessed = processed .map((p) => p.shipment_reference) .join(","); navigator.clipboard .writeText([copyString, copyStringOther, copyStringProcessed].filter(Boolean).join(",")) .then(() => { alert("Parcels copied to clipboard!"); }) .catch((err) => { console.error("Failed to copy:", err); }); }; useEffect(() => { axios .post("/api/expediceListWMS", { batch_id: selectedBatchID }) .then((response) => { setParcels(response.data.batch_items.shipments); setParcelsOther(response.data.batch_items.shipments_other); setLoading(false); }) .catch((error) => { console.error("Error fetching data:", error); setLoading(false); }); }, [selectedBatchID]); // Move a shipment from Parcels to Processed const markAsProcessed = (shipment: ShipmentRequest) => { setParcels((prev) => prev.filter((s) => s.id !== shipment.id)); setProcessed((prev) => [...prev, shipment]); }; // Move a shipment from Processed back to Parcels const markAsUnprocessed = (shipment: ShipmentRequest) => { setProcessed((prev) => prev.filter((s) => s.id !== shipment.id)); setParcels((prev) => [...prev, shipment]); }; // Open the dialog for a shipment const openDialog = ( shipment: ShipmentRequest, type: "parcels" | "processed" ) => { setSelectedShipment(shipment); setSelectedType(type); setIsDialogOpen(true); }; // Close the dialog const closeDialog = () => { setIsDialogOpen(false); setSelectedShipment(null); setSelectedType(null); }; const pdaDialogRef = useRef(null); const openPdaModal = () => { pdaDialogRef.current?.showModal(); }; const closePdaModal = () => { pdaDialogRef.current?.close(); }; const [buttonStates, setButtonStates] = useState<{ [itemId: number]: any }>( {} ); // Handle processing/unprocessing via the dialog action button const handleDialogProcess = () => { if (selectedShipment && selectedType === "parcels") { const filteredButtonStates = selectedShipment.items.reduce( (acc: any, item: Item) => { if (buttonStates[item.id]) { acc[item.id] = buttonStates[item.id]; } return acc; }, {} ); axios .post("/api/warehouseExpedice/passOptions", { shipment_request_id: selectedShipment.id, buttonStates: filteredButtonStates, shipment_items: selectedShipment.items, }) .catch((error) => { console.error("Error sending button states:", error); }); if (parcelRowRefs.current[selectedShipment.id]) { parcelRowRefs.current[selectedShipment.id]?.triggerAnimation(); } else { markAsProcessed(selectedShipment); } } else if (selectedShipment && selectedType === "processed") { if (processedRowRefs.current[selectedShipment.id]) { processedRowRefs.current[selectedShipment.id]?.triggerAnimation(); } else { markAsUnprocessed(selectedShipment); } } closeDialog(); }; if (loading) { return
Loading...
; } return ( <> {/* Top bar */}
Back
{/* Open the modal using document.getElementById('ID').showModal() method */} {/* PDA VIEW DIALOG */}
{/* Tabs */}
{/* Parcels Tab */}
{parcels.length === 0 ? (

No parcels available.

) : ( parcels.map((shipment) => ( { parcelRowRefs.current[shipment.id] = el; }} key={shipment.id} shipment={shipment} onProcess={markAsProcessed} onOpenDialog={openDialog} /> )) )}
{/* Nahravacky Tab */}
{parcelsOther.length === 0 ? (

No parcels available.

) : ( parcelsOther.map((shipment) => ( { parcelRowRefs.current[shipment.id] = el; }} key={shipment.id} shipment={shipment} onProcess={markAsProcessed} onOpenDialog={openDialog} /> )) )}
{/* Processed Tab (15% width) */}
{processed.length === 0 ? (

No processed shipments.

) : ( processed.map((shipment) => ( { processedRowRefs.current[shipment.id] = el; }} key={shipment.id} shipment={shipment} onUnprocess={markAsUnprocessed} onOpenDialog={openDialog} /> )) )}
{/* Shipment Details Dialog */} {selectedShipment && ( )} ); }