|
- "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<Props> = ({
- 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<string>("2/F");
- const [selectedTruck, setSelectedTruck] = useState<string>("");
- 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<StoreLaneSummary | null>(null);
- const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(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<WorkbenchLanePanelPrefs>(() => ({
- ...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<WorkbenchLanePanelPrefs>) => {
- setLanePanelPrefs({ ...lanePanelPrefs, ...patch });
- },
- [lanePanelPrefs, setLanePanelPrefs],
- );
- const [isExtraView, setisExtraView] = useState(false);
- const [etraGroups, setEtraGroups] = useState<WorkbenchEtraShopLaneGroup[]>([]);
- const [isLoadingEtra, setIsLoadingEtra] = useState(false);
- const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState<string | undefined>(undefined);
- const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState<string | undefined>(undefined);
- const [modalInitialShopSearch, setModalInitialShopSearch] = useState<string | undefined>(undefined);
- const [modalTruckXFloor, setModalTruckXFloor] = useState<string | undefined>(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<string, number> = {};
- 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: `<div style="text-align: left; padding: 10px 0;">
- <p><strong>${t("Store")}:</strong> ${storeId}</p>
- <p><strong>${t("Lane Code")}:</strong> ${truckLanceCode}</p>
- ${loadingSequence != null ? `<p><strong>${t("Loading Sequence")}:</strong> ${loadingSequence}</p>` : ""}
- <p><strong>${t("Departure Time")}:</strong> ${truckDepartureTime}</p>
- <p><strong>${t("Required Date")}:</strong> ${dateDisplay}</p>
- <p><strong>${t("Available Orders")}:</strong> ${unassigned}/${total}</p>
- </div>`,
- 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<string, LaneSlot4F[]>();
- 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 = () => (
- <Typography variant="body2" color="text.secondary" sx={{ fontWeight: 600, fontSize: "1rem", textAlign: "center", py: 1 }}>
- {t("No entries available")}
- </Typography>
- );
-
- return (
- <Box sx={{ mb: 2 }}>
- <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: "flex-start", flexWrap: "wrap" }}>
- <Box sx={{ maxWidth: 300 }}>
- <FormControl fullWidth size="small">
- <InputLabel id="date-select-label">{t("Select Date")}</InputLabel>
- <Select labelId="date-select-label" value={selectedDate} label={t("Select Date")} onChange={(e) => patchLanePanelPrefs({ selectedDate: e.target.value as WorkbenchLanePanelPrefs["selectedDate"] })}>
- <MenuItem value="today">{t("Today")} ({getDateLabel(0)})</MenuItem>
- <MenuItem value="tomorrow">{t("Tomorrow")} ({getDateLabel(1)})</MenuItem>
- <MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</MenuItem>
- </Select>
- </FormControl>
- </Box>
- {inEtraUi && (
- <Box sx={{ display: "flex", alignItems: "center", pt: 0.5 }}>
- <Button variant="contained" color="secondary" onClick={() => setEtraMergeDialogOpen(true)}>
- {t("Merge Etra ticket")}
- </Button>
- </Box>
- )}
- {!inEtraUi && (
- <>
- <Box sx={{ minWidth: 140, maxWidth: 300 }}>
- <FormControl fullWidth size="small">
- <InputLabel id="release-type-select-label">{t("Release Type")}</InputLabel>
- <Select labelId="release-type-select-label" value={releaseType} label={t("Release Type")} onChange={(e) => patchLanePanelPrefs({ releaseType: e.target.value })}>
- <MenuItem value="batch">{t("Batch")}</MenuItem>
- <MenuItem value="single">{t("Single")}</MenuItem>
- </Select>
- </FormControl>
- </Box>
- <Box sx={{ minWidth: 120, maxWidth: 200 }}>
- <FormControl fullWidth size="small">
- <InputLabel id="ticket-floor-select-label">{t("Floor ticket")}</InputLabel>
- <Select labelId="ticket-floor-select-label" value={ticketFloor} label={t("Floor ticket")} onChange={(e) => patchLanePanelPrefs({ ticketFloor: e.target.value as WorkbenchLanePanelPrefs["ticketFloor"] })}>
- <MenuItem value="2/F">{t("2F ticket")}</MenuItem>
- <MenuItem value="4/F">{t("4F ticket")}</MenuItem>
- </Select>
- </FormControl>
- </Box>
- {/*
- <Box sx={{ display: "flex", alignItems: "center", pt: 0.5 }}>
- <Button variant="contained" color="secondary" onClick={() => void openEnterEtraView()}>
- {t("isExtra order")}
- </Button>
- </Box>
- */}
- </>
- )}
- {/*
- {inEtraUi && !etraOnly && (
- <Box sx={{ display: "flex", alignItems: "center", pt: 0.5 }}>
- <Button
- variant="outlined"
- onClick={() => {
- clearModalEtraContext();
- setisExtraView(false);
- }}
- >
- {t("Exit Etra view")}
- </Button>
- </Box>
- )}
- {etraOnly && onRequestNormalLaneTab && (
- <Box sx={{ display: "flex", alignItems: "center", pt: 0.5 }}>
- <Button variant="outlined" onClick={() => onRequestNormalLaneTab()}>
- {t("Back to normal assign tab")}
- </Button>
- </Box>
- )}
- */}
- </Stack>
-
- {!inEtraUi ? (
- <Grid container spacing={2}>
- {ticketFloor === "2/F" && (
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>2/F</Typography>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {isLoadingSummary ? <Typography variant="caption">{t("Loading...")}</Typography> : !summary2F?.rows?.length ? renderNoEntry() : (
- <Grid container spacing={1}>
- {summary2F.rows.map((row) => (
- <Grid item xs={12} key={row.truckDepartureTime}>
- <Stack direction={{ xs: "column", sm: "row" }} spacing={1} alignItems={{ xs: "stretch", sm: "center" }} sx={{ border: "1px solid #e0e0e0", borderRadius: 0.5, p: 1, backgroundColor: "#fff" }}>
- <Typography variant="body2" sx={{ fontWeight: 600, minWidth: { sm: 60 } }}>{row.truckDepartureTime}</Typography>
- <Stack direction="row" flexWrap="wrap" sx={{ gap: 1 }}>
- {row.lanes.map((lane) => (
- <Button key={`${row.truckDepartureTime}-${lane.truckLanceCode}`} variant="outlined" disabled={lane.unassigned === 0 || isAssigning} onClick={() => void handleLaneButtonClick("2/F", row.truckDepartureTime, lane.truckLanceCode, null, selectedDate, lane.unassigned, lane.total)}>
- {`${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`}
- </Button>
- ))}
- </Stack>
- </Stack>
- </Grid>
- ))}
- </Grid>
- )}
- </Box>
- </Stack>
- </Grid>
- )}
-
- {ticketFloor === "4/F" && (
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>4/F</Typography>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {isLoadingSummary ? <Typography variant="caption">{t("Loading...")}</Typography> : !truckGroups4F.length ? renderNoEntry() : (
- <Grid container spacing={1}>
- {truckGroups4F.map(({ truckLanceCode, slots }) => (
- <Grid item xs={12} key={truckLanceCode}>
- <Stack direction={{ xs: "column", sm: "row" }} spacing={1} alignItems={{ xs: "stretch", sm: "center" }} sx={{ border: "1px solid #e0e0e0", borderRadius: 0.5, p: 1, backgroundColor: "#fff" }}>
- <Typography variant="body2" sx={{ fontWeight: 700, minWidth: { sm: 160 } }}>{truckLanceCode}</Typography>
- <Stack direction="row" flexWrap="wrap" sx={{ gap: 1 }}>
- {slots.map((slot) => {
- const handlerName = (slot.lane.handlerName ?? "").trim();
- return (
- <Button key={`${truckLanceCode}-${slot.sequenceIndex}-${slot.truckDepartureTime}`} variant="outlined" disabled={slot.lane.unassigned === 0 || isAssigning} onClick={() => void handleLaneButtonClick("4/F", slot.truckDepartureTime, slot.lane.truckLanceCode, slot.lane.loadingSequence ?? null, selectedDate, slot.lane.unassigned, slot.lane.total)}>
- {`${t("Loading sequence n", { n: slot.lane.loadingSequence ?? slot.sequenceIndex })} (${slot.lane.unassigned}/${slot.lane.total})${handlerName ? ` ${handlerName}` : ""}`}
- </Button>
- );
- })}
- </Stack>
- </Stack>
- </Grid>
- ))}
- </Grid>
- )}
- </Box>
- </Stack>
- </Grid>
- )}
-
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Stack sx={{ minWidth: 60, pt: 1 }} spacing={0.25}>
- <Typography sx={{ fontWeight: 600 }}>{t("Truck X")}</Typography>
- {/*
- <Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.2 }}>
- {t("Required Date")}: {selectedDeliveryDateYmd}
- </Typography>
- */}
- </Stack>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {defaultTruckCount === 0 ? (
- renderNoEntry()
- ) : (
- <Button
- variant="outlined"
- onClick={() => {
- clearModalEtraContext();
- setSelectedStore("");
- setSelectedTruck("車線-X");
- setIsDefaultTruck(true);
- setDefaultDateScope("today");
- setModalTruckXFloor(ticketFloorApiKey);
- setModalOpen(true);
- }}
- >
- {/*{`${selectedDeliveryDateYmd} (${defaultTruckCount})`}*/}
- {t("車線-X")}({defaultTruckCount})
- </Button>
- )}
- </Box>
- </Stack>
- </Grid>
-
- <Grid item xs={12}>
- <Box sx={{ py: 2, mt: 1, mb: 0.5, borderTop: "1px solid #e0e0e0" }}>
- <Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 0.5 }}>
- {t("Not yet finished released do pick orders")}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {t("Released orders not yet completed - click lane to select and assign")}
- </Typography>
- </Box>
- </Grid>
-
- {ticketFloor === "2/F" && (
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>2/F</Typography>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {truckCounts2F.length === 0 ? renderNoEntry() : (
- <Grid container spacing={1}>
- {truckCounts2F.map(({ truck, count }) => (
- <Grid item xs={6} sm={4} md={3} key={`2F-${truck}`} sx={{ display: "flex" }}>
- <Button
- variant="outlined"
- onClick={() => {
- clearModalEtraContext();
- setIsDefaultTruck(false);
- setSelectedStore("2/F");
- setSelectedTruck(truck);
- setModalOpen(true);
- }}
- sx={{ flex: 1 }}
- >
- {`${truck} (${count})`}
- </Button>
- </Grid>
- ))}
- </Grid>
- )}
- </Box>
- </Stack>
- </Grid>
- )}
-
- {ticketFloor === "4/F" && (
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>4/F</Typography>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {truckCounts4F.length === 0 ? renderNoEntry() : (
- <Grid container spacing={1}>
- {truckCounts4F.map(({ truck, count }) => (
- <Grid item xs={6} sm={4} md={3} key={`4F-${truck}`} sx={{ display: "flex" }}>
- <Button
- variant="outlined"
- onClick={() => {
- clearModalEtraContext();
- setIsDefaultTruck(false);
- setSelectedStore("4/F");
- setSelectedTruck(truck);
- setModalOpen(true);
- }}
- sx={{ flex: 1 }}
- >
- {`${truck} (${count})`}
- </Button>
- </Grid>
- ))}
- </Grid>
- )}
- </Box>
- </Stack>
- </Grid>
- )}
-
- <Grid item xs={12}>
- <Stack direction="row" spacing={2} alignItems="flex-start">
- <Stack sx={{ minWidth: 60, pt: 1 }} spacing={0.25}>
- <Typography sx={{ fontWeight: 600 }}>{t("Truck X")}</Typography>
- <Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.2 }}>
- {t("Before today")}
- </Typography>
- </Stack>
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}>
- {beforeTodayTruckXCount === 0 ? (
- renderNoEntry()
- ) : (
- <Button
- variant="outlined"
- onClick={() => {
- clearModalEtraContext();
- setSelectedStore("");
- setSelectedTruck("車線-X");
- setIsDefaultTruck(true);
- setDefaultDateScope("before");
- setModalTruckXFloor(ticketFloorApiKey);
- setModalOpen(true);
- }}
- >
- {`${t("車線-X")} (${beforeTodayTruckXCount})`}
- </Button>
- )}
- </Box>
- </Stack>
- </Grid>
- </Grid>
- ) : (
- <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 2, backgroundColor: "#fafafa" }}>
- {isLoadingEtra ? (
- <Typography variant="caption">{t("Loading...")}</Typography>
- ) : etraGroups.length === 0 ? (
- renderNoEntry()
- ) : (
- <Grid container spacing={2}>
- {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 (
- <Grid item xs={12} sm={6} md={4} lg={3} key={tileKey}>
- <Button
- fullWidth
- variant="outlined"
- disabled={lane.unassigned === 0 || !sid}
- onClick={() => {
- setModalReleaseTypeFilter("isExtra");
- setModalFilterRequiredDeliveryDate(selectedDeliveryDateYmd);
- setModalInitialShopSearch((group.shopName || group.shopCode || "").trim() || undefined);
- setSelectedStore(sid);
- setSelectedTruck(lane.truckLanceCode);
- setIsDefaultTruck(false);
- setDefaultDateScope("today");
- setModalOpen(true);
- }}
- sx={{
- height: "100%",
- minHeight: 72,
- py: 1.25,
- px: 1.5,
- display: "flex",
- flexDirection: "column",
- alignItems: "stretch",
- justifyContent: "flex-start",
- textAlign: "left",
- textTransform: "none",
- whiteSpace: "normal",
- borderColor: "secondary.main",
- }}
- >
- <Typography
- variant="subtitle2"
- component="span"
- color="secondary"
- sx={{ fontWeight: 700, lineHeight: 1.35, wordBreak: "break-word" }}
- >
- {shopPrimary}
- </Typography>
- <Typography
- variant="caption"
- component="span"
- color="text.secondary"
- sx={{ mt: 0.5, lineHeight: 1.35, wordBreak: "break-word" }}
- >
- {laneSecondary}
- </Typography>
- </Button>
- </Grid>
- );
- }),
- )}
- </Grid>
- )}
- </Box>
- )}
-
- <ReleasedDoPickOrderSelectModal
- open={modalOpen}
- storeId={selectedStore}
- truck={selectedTruck}
- isDefaultTruck={isDefaultTruck}
- defaultDateScope={defaultDateScope}
- defaultTruckRequiredDeliveryDate={selectedDeliveryDateYmd}
- listBridge={workbenchReleasedListBridge}
- releaseTypeFilter={modalReleaseTypeFilter}
- filterRequiredDeliveryDate={modalFilterRequiredDeliveryDate}
- initialShopSearch={modalInitialShopSearch}
- truckXFloor={modalTruckXFloor}
- onClose={() => {
- setModalOpen(false);
- clearModalEtraContext();
- }}
- onAssigned={() => {
- if (inEtraUi) void loadEtraSummaries();
- else void loadSummaries();
- onPickOrderAssigned?.();
- onSwitchToDetailTab?.();
- }}
- />
- <WorkbenchEtraMergeDialog
- open={etraMergeDialogOpen}
- onClose={() => setEtraMergeDialogOpen(false)}
- initialDateYmd={selectedDeliveryDateYmd}
- onMerged={() => {
- void loadEtraSummaries();
- onPickOrderAssigned?.();
- window.dispatchEvent(new Event("pickOrderAssigned"));
- }}
- />
- </Box>
- );
- };
-
- export default WorkbenchFloorLanePanel;
|