vat_wms/resources/js/Components/ShipmentItemsAccordion.tsx

293 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useRef } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBoxesStacked,
faCheckCircle,
faCheckToSlot,
faClipboardQuestion,
faShuffle,
} from "@fortawesome/free-solid-svg-icons";
import { ShipmentRequest } from "@/interfaces/interfaces";
interface ShipmentItemsAccordionProps {
selectedShipment: ShipmentRequest;
buttonStates: { [itemId: number]: any };
setButtonStates: React.Dispatch<React.SetStateAction<{ [itemId: number]: any }>>;
parentModalHandle: () => void;
}
export default function ShipmentItemsAccordion({
selectedShipment,
buttonStates,
setButtonStates,
parentModalHandle,
}: ShipmentItemsAccordionProps) {
// Track which accordion item (by id) is open. Default to first item.
const [openAccordion, setOpenAccordion] = useState<number | null>(
selectedShipment.items[0]?.id || null
);
// Track completed items by their id.
const [completedItems, setCompletedItems] = useState<{ [id: number]: boolean }>({});
// Ref to store a hold timer for touch devices.
const holdTimerRef = useRef<number | null>(null);
// Modal state
const [modalPositionOpen, setModalPositionOpen] = useState(false);
const [currentItem, setCurrentItem] = useState<any>(null);
const [fromPosition, setFromPosition] = useState<string>("");
const [toPosition, setToPosition] = useState<string>("");
// modalMode: "full" or "select-only"
const [modalMode, setModalMode] = useState<"full" | "select-only">("full");
// modalKey: "neniNaPozici" | "doskladnit" | "zmenitPozici"
const [modalKey, setModalKey] = useState<string | null>(null);
// Helper to get or initialize the button state for an item
const getItemState = (itemId: number) =>
buttonStates[itemId] || {
neniNaPozici: { value: false, details: [] },
doskladnit: { value: false, details: [] },
zmenitPozici: { value: false, details: [] },
};
// Generic update function for button state for a given item and key.
const updateButtonState = (itemId: number, key: string, details: any[] = []) => {
setButtonStates((prev) => {
const currentState = getItemState(itemId);
const currentButtonState = currentState[key] || { value: false, details: [] };
const toggledValue = !currentButtonState.value;
return {
...prev,
[itemId]: {
...currentState,
[key]: { value: toggledValue, details: toggledValue ? details : [] },
},
};
});
};
// Open the modal with appropriate mode and key
const handleOpenModal = (
item: any,
key: "neniNaPozici" | "doskladnit" | "zmenitPozici",
mode: "full" | "select-only"
) => {
setCurrentItem(item);
setModalKey(key);
setModalMode(mode);
setModalPositionOpen(true);
// Optionally notify parent modal
// parentModalHandle();
};
// Confirm handler for the modal
const handleConfirmModal = () => {
if (currentItem && modalKey) {
if (modalMode === "full") {
updateButtonState(currentItem.id, modalKey, [
{ from: fromPosition, to: toPosition },
]);
} else {
// select-only: only pass the "from" detail
updateButtonState(currentItem.id, modalKey, [{ position: fromPosition }]);
}
}
// Reset modal state
setModalPositionOpen(false);
setCurrentItem(null);
setFromPosition("");
setToPosition("");
setModalMode("full");
setModalKey(null);
// Optionally notify parent modal
// parentModalHandle();
};
// Toggle the open state of an accordion item.
const handleToggle = (id: number) => {
if (openAccordion === id) {
setOpenAccordion(null);
} else {
setOpenAccordion(id);
}
};
// Mark an item as completed.
const handleComplete = (id: number) => {
setCompletedItems((prev) => ({ ...prev, [id]: true }));
if (openAccordion === id) {
setOpenAccordion(null);
}
};
// Touch event handlers for long press.
const onTouchStartHandler = (id: number) => {
holdTimerRef.current = window.setTimeout(() => {
handleComplete(id);
}, 1000);
};
const onTouchEndHandler = () => {
if (holdTimerRef.current) {
clearTimeout(holdTimerRef.current);
holdTimerRef.current = null;
}
};
return (
<div className="space-y-4">
{selectedShipment.items.map((item) => {
const itemState = getItemState(item.id);
const isOpen = openAccordion === item.id;
const isCompleted = completedItems[item.id];
// If an item is not open, it appears "greyed out"
const headerOpacity = isOpen ? "opacity-100" : "opacity-50";
const buttonFunctionality = isOpen ? "" : "pointer-events-none";
return (
<div key={item.id} className="space-y-2">
<div
className={`collapse collapse-arrow border border-t-2 border-warning border-b-0 border-l-0 border-r-0 bg-base-100 ${headerOpacity}`}
>
<input
type="checkbox"
checked={isOpen}
onChange={() => handleToggle(item.id)}
className="peer"
/>
<div
className="collapse-title flex justify-between items-center text-base-content px-4 py-2 cursor-pointer"
onDoubleClick={() => handleComplete(item.id)}
onTouchStart={() => onTouchStartHandler(item.id)}
onTouchEnd={onTouchEndHandler}
>
<div>
<p className="font-semibold text-base-content">
{item.quantity}× {item.name}
</p>
<p className="text-sm text-base-content">{item.item_note}</p>
<div className="flex justify-between text-sm text-base-content mt-1">
<span>{item.model_number}</span>
<span>
{(item.price / item.quantity).toFixed(2)} {selectedShipment.currency}
</span>
</div>
</div>
{isCompleted && (
<FontAwesomeIcon icon={faCheckCircle} className="text-success" />
)}
</div>
<div className="collapse-content bg-base-200 text-base-content p-4">
<div className="flex flex-wrap gap-4 border border-base-300 rounded bg-base-100 p-3">
{Object.entries(item.stockData).flatMap(([stockName, stockArray]) =>
stockArray.map((stock, idx) => (
<div key={`${stockName}-${idx}`} className="text-sm">
<p className="font-semibold">{stockName}</p>
<p>Location: {stock.location}</p>
<p>Count: {stock.count} ks</p>
</div>
))
)}
</div>
</div>
</div>
<div className={`flex flex-wrap items-center gap-2 text-base-content ${headerOpacity}`}>
<button
onClick={() =>
handleOpenModal(item, "neniNaPozici", "select-only")
}
className={`btn btn-outline btn-sm ${!isOpen ? "pointer-events-none" : ""} ${itemState.neniNaPozici.value ? "btn-success" : ""} w-full sm:w-auto`}
>
<FontAwesomeIcon icon={faClipboardQuestion} className="mr-2" />
Není na pozici
</button>
<button
onClick={() =>
handleOpenModal(item, "doskladnit", "select-only")
}
className={`btn btn-outline btn-sm ${!isOpen ? "pointer-events-none" : ""} ${itemState.doskladnit.value ? "btn-success" : ""} w-full sm:w-auto`}
>
<FontAwesomeIcon icon={faBoxesStacked} className="mr-2" />
Doplnit
</button>
<button
onClick={() =>
handleOpenModal(item, "zmenitPozici", "full")
}
className={`btn btn-outline btn-sm ${!isOpen ? "pointer-events-none" : ""} ${itemState.zmenitPozici.value ? "btn-success" : ""} w-full sm:w-auto`}
>
<FontAwesomeIcon icon={faShuffle} className="mr-2" />
Změnit pozici
</button>
<button
onClick={() => handleComplete(item.id)}
className={`btn btn-outline btn-sm ml-auto ${buttonFunctionality} w-full sm:w-auto`}
>
<FontAwesomeIcon icon={faCheckToSlot} className="text-lg" />
</button>
</div>
</div>
);
})}
{/* Modal for changing position */}
<div className={`modal ${modalPositionOpen ? "modal-open" : ""}`}>
<div className="modal-box max-w-md">
<h3 className="font-bold text-lg">Change Position</h3>
<div className="space-y-4 mt-4">
<div>
<label className="block text-sm font-medium text-base-content mb-1">
From Position
</label>
<select
className="select select-bordered w-full"
value={fromPosition}
onChange={(e) => setFromPosition(e.target.value)}
>
<option value="">Select position</option>
{currentItem &&
Object.entries(currentItem.stockData).flatMap(
([stockName, stockArray]) =>
stockArray.map((stock, idx) => (
<option
key={`${stockName}-${idx}`}
value={`${stockName}--${stock.location}`}
>
<strong>{stockName}</strong> --- {stock.location}
</option>
))
)}
</select>
</div>
{modalMode === "full" && (
<div>
<label className="block text-sm font-medium text-base-content mb-1">
To Position
</label>
<input
type="text"
className="input input-bordered w-full"
value={toPosition}
onChange={(e) => setToPosition(e.target.value)}
placeholder="Enter new position"
/>
</div>
)}
</div>
<div className="modal-action mt-4 flex justify-end gap-2">
<button className="btn btn-primary btn-sm" onClick={handleConfirmModal}>
Confirm
</button>
<button
className="btn btn-outline btn-sm"
onClick={() => setModalPositionOpen(false)}
>
Cancel
</button>
</div>
</div>
</div>
</div>
);
}