"use client"; import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel } from "@mui/material"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import type { StoreLaneSummary, LaneRow, LaneBtn } from "@/app/api/pickOrder/actions"; import { assignByDeliveryOrderPickOrderId, assignWorkbenchByLane, fetchWorkbenchEtraLaneSummary, fetchWorkbenchReleasedDoPickOrdersForSelection, fetchWorkbenchReleasedDoPickOrdersForSelectionToday, fetchWorkbenchStoreLaneSummary, type WorkbenchEtraShopLaneGroup, } from "@/app/api/doworkbench/actions"; import Swal from "sweetalert2"; import dayjs from "dayjs"; import ReleasedDoPickOrderSelectModal from "@/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal"; import { DEFAULT_WORKBENCH_LANE_PANEL_PREFS, type WorkbenchLanePanelPrefs, } from "./workbenchLanePanelPrefs"; import WorkbenchEtraMergeDialog from "./WorkbenchEtraMergeDialog"; interface Props { onPickOrderAssigned?: () => void; onSwitchToDetailTab?: () => void; initialReleaseType?: string; /** When true (workbench tab "Etra"), only the Etra shop×lane grid is shown; use top tab to return to normal. */ etraOnly?: boolean; /** With [etraOnly], navigates to normal assign tab (tab 0). */ onRequestNormalLaneTab?: () => void; /** Lifted from DoWorkbenchTabs — persists date/floor/release across workbench tab switches. */ lanePanelPrefs?: WorkbenchLanePanelPrefs; onLanePanelPrefsChange?: (prefs: WorkbenchLanePanelPrefs) => void; } type LaneSlot4F = { truckDepartureTime: string; lane: LaneBtn }; type TruckGroup4F = { truckLanceCode: string; slots: (LaneSlot4F & { sequenceIndex: number })[] }; const WorkbenchFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSwitchToDetailTab, initialReleaseType = "batch", etraOnly = false, onRequestNormalLaneTab, lanePanelPrefs: lanePanelPrefsProp, onLanePanelPrefsChange, }) => { const { t } = useTranslation("pickOrder"); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; const [selectedStore, setSelectedStore] = useState("2/F"); const [selectedTruck, setSelectedTruck] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); const [truckCounts4F, setTruckCounts4F] = useState<{ truck: string; count: number }[]>([]); const [summary2F, setSummary2F] = useState(null); const [summary4F, setSummary4F] = useState(null); const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("today"); const [isLoadingSummary, setIsLoadingSummary] = useState(false); const [isAssigning, setIsAssigning] = useState(false); const [isDefaultTruck, setIsDefaultTruck] = useState(false); const [beforeTodayTruckXCount, setBeforeTodayTruckXCount] = useState(0); const [internalLanePrefs, setInternalLanePrefs] = useState(() => ({ ...DEFAULT_WORKBENCH_LANE_PANEL_PREFS, releaseType: initialReleaseType, })); const lanePanelPrefs = lanePanelPrefsProp ?? internalLanePrefs; const setLanePanelPrefs = onLanePanelPrefsChange ?? setInternalLanePrefs; const selectedDate = lanePanelPrefs.selectedDate; const releaseType = lanePanelPrefs.releaseType; const ticketFloor = lanePanelPrefs.ticketFloor; const patchLanePanelPrefs = useCallback( (patch: Partial) => { setLanePanelPrefs({ ...lanePanelPrefs, ...patch }); }, [lanePanelPrefs, setLanePanelPrefs], ); const [isExtraView, setisExtraView] = useState(false); const [etraGroups, setEtraGroups] = useState([]); const [isLoadingEtra, setIsLoadingEtra] = useState(false); const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState(undefined); const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState(undefined); const [modalInitialShopSearch, setModalInitialShopSearch] = useState(undefined); const [modalTruckXFloor, setModalTruckXFloor] = useState(undefined); const ticketFloorApiKey = useMemo( () => ticketFloor.replace("/", "").trim().toUpperCase(), [ticketFloor], ); const defaultTruckCount = (ticketFloor === "2/F" ? summary2F?.defaultTruckCount : summary4F?.defaultTruckCount) ?? 0; const etraEnterInFlightRef = useRef(false); const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); const inEtraUi = useMemo(() => etraOnly || isExtraView, [etraOnly, isExtraView]); const selectedDeliveryDateYmd = useMemo(() => { if (selectedDate === "today") return dayjs().format("YYYY-MM-DD"); if (selectedDate === "tomorrow") return dayjs().add(1, "day").format("YYYY-MM-DD"); if (selectedDate === "dayAfterTomorrow") return dayjs().add(2, "day").format("YYYY-MM-DD"); return dayjs().format("YYYY-MM-DD"); }, [selectedDate]); const hasLoggedRef = useRef(false); const fullReadyLoggedRef = useRef(false); const pendingRef = useRef(0); const workbenchReleasedListBridge = useMemo( () => ({ loadBeforeToday: ( shopName?: string, storeId?: string, truck?: string, releaseType?: string, floor?: string ) => fetchWorkbenchReleasedDoPickOrdersForSelection(shopName, storeId, truck, releaseType, floor), loadToday: ( shopName?: string, storeId?: string, truck?: string, requiredDeliveryDate?: string, releaseType?: string, floor?: string ) => fetchWorkbenchReleasedDoPickOrdersForSelectionToday( shopName, storeId, truck, requiredDeliveryDate, releaseType, floor ), assignByListItemId: assignByDeliveryOrderPickOrderId, }), [], ); const startFullTimer = () => { if (typeof window === "undefined") return; const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; if (!(window as any)[key]) { (window as any)[key] = true; console.time("[FG] FloorLanePanel full ready"); } }; const tryEndFullTimer = () => { if (typeof window === "undefined") return; const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; if ((window as any)[key] && !fullReadyLoggedRef.current && pendingRef.current === 0) { fullReadyLoggedRef.current = true; console.timeEnd("[FG] FloorLanePanel full ready"); delete (window as any)[key]; } }; const loadSummaries = useCallback(async () => { setIsLoadingSummary(true); pendingRef.current += 1; startFullTimer(); try { let dateParam: string | undefined; if (selectedDate === "today") dateParam = dayjs().format("YYYY-MM-DD"); else if (selectedDate === "tomorrow") dateParam = dayjs().add(1, "day").format("YYYY-MM-DD"); else if (selectedDate === "dayAfterTomorrow") dateParam = dayjs().add(2, "day").format("YYYY-MM-DD"); const [s2, s4] = await Promise.all([ fetchWorkbenchStoreLaneSummary("2/F", dateParam, releaseType), fetchWorkbenchStoreLaneSummary("4/F", dateParam, releaseType), ]); setSummary2F(s2); setSummary4F(s4); } catch (error) { console.error("Error loading summaries:", error); } finally { setIsLoadingSummary(false); pendingRef.current -= 1; tryEndFullTimer(); if (!hasLoggedRef.current) { hasLoggedRef.current = true; } } }, [selectedDate, releaseType]); const loadEtraSummaries = useCallback(async () => { setIsLoadingEtra(true); pendingRef.current += 1; startFullTimer(); try { let dateParam: string | undefined; if (selectedDate === "today") dateParam = dayjs().format("YYYY-MM-DD"); else if (selectedDate === "tomorrow") dateParam = dayjs().add(1, "day").format("YYYY-MM-DD"); else if (selectedDate === "dayAfterTomorrow") dateParam = dayjs().add(2, "day").format("YYYY-MM-DD"); const data = await fetchWorkbenchEtraLaneSummary(dateParam); setEtraGroups(data); } catch (error) { console.error("Error loading Etra summary:", error); setEtraGroups([]); } finally { setIsLoadingEtra(false); pendingRef.current -= 1; tryEndFullTimer(); } }, [selectedDate]); useEffect(() => { if (inEtraUi) void loadEtraSummaries(); else void loadSummaries(); }, [inEtraUi, loadEtraSummaries, loadSummaries]); useEffect(() => { const loadCounts = async () => { if (inEtraUi) return; pendingRef.current += 1; startFullTimer(); try { const [list2F, list4F] = await Promise.all([ fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, "2/F"), fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, "4/F"), ]); const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { const map: Record = {}; list.forEach((item) => { const lane = item.truckLanceCode || "-"; map[lane] = (map[lane] || 0) + 1; }); return Object.entries(map) .map(([truck, count]) => ({ truck, count })) .sort((a, b) => a.truck.localeCompare(b.truck)); }; setTruckCounts2F(groupByTruck(list2F)); setTruckCounts4F(groupByTruck(list4F)); } catch (e) { console.error("Error loading counts:", e); setTruckCounts2F([]); setTruckCounts4F([]); } finally { pendingRef.current -= 1; tryEndFullTimer(); } }; void loadCounts(); }, [inEtraUi, loadSummaries]); useEffect(() => { const loadBeforeTodayTruckX = async () => { if (inEtraUi) return; pendingRef.current += 1; startFullTimer(); try { // storeId stays undefined (Truck X); floor splits by DO supplier preferred floor. const list = await fetchWorkbenchReleasedDoPickOrdersForSelection( undefined, undefined, "車線-X", undefined, ticketFloorApiKey ); setBeforeTodayTruckXCount(list.length); } catch { setBeforeTodayTruckXCount(0); } finally { pendingRef.current -= 1; tryEndFullTimer(); } }; void loadBeforeTodayTruckX(); }, [inEtraUi, ticketFloorApiKey]); const clearModalEtraContext = useCallback(() => { setModalReleaseTypeFilter(undefined); setModalFilterRequiredDeliveryDate(undefined); setModalInitialShopSearch(undefined); setModalTruckXFloor(undefined); }, []); const openEnterEtraView = useCallback(async () => { if (etraEnterInFlightRef.current) return; etraEnterInFlightRef.current = true; try { /* const r = await Swal.fire({ title: t("Enter isExtra workbench view?"), text: t("Etra view groups all add-on tickets by shop and lane for the selected date."), icon: "question", showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438", }); if (r.isConfirmed) setisExtraView(true); */ setisExtraView(true); } finally { etraEnterInFlightRef.current = false; } }, []); const handleAssignByLane = useCallback( async ( storeId: string, truckDepartureTime: string, truckLanceCode: string, loadingSequence: number | null | undefined, requiredDate: string, ) => { if (!currentUserId) return; let dateParam: string | undefined; if (requiredDate === "today") dateParam = dayjs().format("YYYY-MM-DD"); else if (requiredDate === "tomorrow") dateParam = dayjs().add(1, "day").format("YYYY-MM-DD"); else if (requiredDate === "dayAfterTomorrow") dateParam = dayjs().add(2, "day").format("YYYY-MM-DD"); setIsAssigning(true); try { const res = await assignWorkbenchByLane({ userId: currentUserId, storeId, truckLanceCode, truckDepartureTime, loadingSequence, requiredDate: dateParam, releaseType: inEtraUi ? "isExtra" : releaseType, }); console.log("assignByLane result:", res); if (res.code === "SUCCESS") { window.dispatchEvent(new CustomEvent("pickOrderAssigned")); void (inEtraUi ? loadEtraSummaries() : loadSummaries()); onPickOrderAssigned?.(); onSwitchToDetailTab?.(); } } catch { await Swal.fire({ icon: "error", title: t("Error"), text: t("Error occurred during assignment."), confirmButtonText: t("Confirm"), confirmButtonColor: "#8dba00" }); } finally { setIsAssigning(false); } }, [currentUserId, inEtraUi, loadEtraSummaries, loadSummaries, onPickOrderAssigned, onSwitchToDetailTab, releaseType, t], ); const handleLaneButtonClick = useCallback( async ( storeId: string, truckDepartureTime: string, truckLanceCode: string, loadingSequence: number | null | undefined, requiredDate: string, unassigned: number, total: number, ) => { let dateDisplay = requiredDate; if (requiredDate === "today") dateDisplay = dayjs().format("YYYY-MM-DD"); else if (requiredDate === "tomorrow") dateDisplay = dayjs().add(1, "day").format("YYYY-MM-DD"); else if (requiredDate === "dayAfterTomorrow") dateDisplay = dayjs().add(2, "day").format("YYYY-MM-DD"); const result = await Swal.fire({ title: t("Confirm Assignment"), html: `

${t("Store")}: ${storeId}

${t("Lane Code")}: ${truckLanceCode}

${loadingSequence != null ? `

${t("Loading Sequence")}: ${loadingSequence}

` : ""}

${t("Departure Time")}: ${truckDepartureTime}

${t("Required Date")}: ${dateDisplay}

${t("Available Orders")}: ${unassigned}/${total}

`, icon: "question", showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), confirmButtonColor: "#8dba00", cancelButtonColor: "#F04438", }); if (result.isConfirmed) { await handleAssignByLane(storeId, truckDepartureTime, truckLanceCode, loadingSequence, requiredDate); } }, [handleAssignByLane, t], ); const getDateLabel = (offset: number) => dayjs().add(offset, "day").format("YYYY-MM-DD"); const truckGroups4F = useMemo((): TruckGroup4F[] => { const rows = summary4F?.rows as LaneRow[] | undefined; if (!rows?.length) return []; const map = new Map(); for (const row of rows) { for (const lane of row.lanes) { const list = map.get(lane.truckLanceCode); const slot: LaneSlot4F = { truckDepartureTime: row.truckDepartureTime, lane }; if (list) list.push(slot); else map.set(lane.truckLanceCode, [slot]); } } return Array.from(map.entries()).map(([truckLanceCode, slots]) => ({ truckLanceCode, slots: slots .slice() .sort((a, b) => (a.lane.loadingSequence ?? 999) - (b.lane.loadingSequence ?? 999)) .map((s, i) => ({ ...s, sequenceIndex: i + 1 })), })); }, [summary4F?.rows]); const renderNoEntry = () => ( {t("No entries available")} ); return ( {t("Select Date")} {inEtraUi && ( )} {!inEtraUi && ( <> {t("Release Type")} {t("Floor ticket")} {/* */} )} {/* {inEtraUi && !etraOnly && ( )} {etraOnly && onRequestNormalLaneTab && ( )} */} {!inEtraUi ? ( {ticketFloor === "2/F" && ( 2/F {isLoadingSummary ? {t("Loading...")} : !summary2F?.rows?.length ? renderNoEntry() : ( {summary2F.rows.map((row) => ( {row.truckDepartureTime} {row.lanes.map((lane) => ( ))} ))} )} )} {ticketFloor === "4/F" && ( 4/F {isLoadingSummary ? {t("Loading...")} : !truckGroups4F.length ? renderNoEntry() : ( {truckGroups4F.map(({ truckLanceCode, slots }) => ( {truckLanceCode} {slots.map((slot) => { const handlerName = (slot.lane.handlerName ?? "").trim(); return ( ); })} ))} )} )} {t("Truck X")} {/* {t("Required Date")}: {selectedDeliveryDateYmd} */} {defaultTruckCount === 0 ? ( renderNoEntry() ) : ( )} {t("Not yet finished released do pick orders")} {t("Released orders not yet completed - click lane to select and assign")} {ticketFloor === "2/F" && ( 2/F {truckCounts2F.length === 0 ? renderNoEntry() : ( {truckCounts2F.map(({ truck, count }) => ( ))} )} )} {ticketFloor === "4/F" && ( 4/F {truckCounts4F.length === 0 ? renderNoEntry() : ( {truckCounts4F.map(({ truck, count }) => ( ))} )} )} {t("Truck X")} {t("Before today")} {beforeTodayTruckXCount === 0 ? ( renderNoEntry() ) : ( )} ) : ( {isLoadingEtra ? ( {t("Loading...")} ) : etraGroups.length === 0 ? ( renderNoEntry() ) : ( {etraGroups.flatMap((group) => group.lanes.map((lane, li) => { const sid = (lane.storeId ?? "").trim(); const dep = (lane.truckDepartureTime ?? "").trim(); const is4F = sid.replace(/\//g, "").toUpperCase() === "4F"; const labelCore = is4F && lane.loadingSequence != null ? `${t("Loading sequence n", { n: lane.loadingSequence })} (${lane.unassigned}/${lane.total})` : `${dep ? `${dep} ` : ""}${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`; const handlerName = (lane.handlerName ?? "").trim(); const shopCode = (group.shopCode ?? "").trim(); const shopName = (group.shopName ?? "").trim(); const shopPrimary = shopCode && shopName && shopCode !== shopName ? `${shopCode} · ${shopName}` : shopName || shopCode || t("Shop"); const laneSecondary = `${labelCore}${handlerName ? ` · ${handlerName}` : ""}`; const tileKey = `${shopCode}|${shopName}|${lane.truckLanceCode}|${dep}|${lane.loadingSequence ?? ""}|${li}`; return ( ); }), )} )} )} { setModalOpen(false); clearModalEtraContext(); }} onAssigned={() => { if (inEtraUi) void loadEtraSummaries(); else void loadSummaries(); onPickOrderAssigned?.(); onSwitchToDetailTab?.(); }} /> setEtraMergeDialogOpen(false)} initialDateYmd={selectedDeliveryDateYmd} onMerged={() => { void loadEtraSummaries(); onPickOrderAssigned?.(); window.dispatchEvent(new Event("pickOrderAssigned")); }} /> ); }; export default WorkbenchFloorLanePanel;