| @@ -10,18 +10,25 @@ import { | |||
| } from "@/app/api/pickOrder/actions"; | |||
| import WorkbenchFloorLanePanel from "./WorkbenchFloorLanePanel"; | |||
| import WorkbenchGoodPickExecutionDetail from "./WorkbenchGoodPickExecutionDetail"; | |||
| import type { WorkbenchLanePanelPrefs } from "./workbenchLanePanelPrefs"; | |||
| export type DoWorkbenchPickShellLaneMode = "normal" | "etra"; | |||
| type DoWorkbenchPickShellProps = { | |||
| /** Tab 0: normal 2F/4F lane grid; tab 1: Etra-only lane grid */ | |||
| laneMode?: DoWorkbenchPickShellLaneMode; | |||
| lanePanelPrefs: WorkbenchLanePanelPrefs; | |||
| onLanePanelPrefsChange: (prefs: WorkbenchLanePanelPrefs) => void; | |||
| }; | |||
| /** | |||
| * FG workbench: 未指派顯示樓層/車線指派;已指派顯示揀貨明細(workbench API)。 | |||
| */ | |||
| const DoWorkbenchPickShell: React.FC<DoWorkbenchPickShellProps> = ({ laneMode = "normal" }) => { | |||
| const DoWorkbenchPickShell: React.FC<DoWorkbenchPickShellProps> = ({ | |||
| laneMode = "normal", | |||
| lanePanelPrefs, | |||
| onLanePanelPrefsChange, | |||
| }) => { | |||
| const { data: session, status } = useSession() as { | |||
| data: SessionWithTokens | null; | |||
| status: "loading" | "authenticated" | "unauthenticated"; | |||
| @@ -104,6 +111,8 @@ const DoWorkbenchPickShell: React.FC<DoWorkbenchPickShellProps> = ({ laneMode = | |||
| onPickOrderAssigned={() => void refreshWorkbenchView()} | |||
| etraOnly={laneMode === "etra"} | |||
| onRequestNormalLaneTab={laneMode === "etra" ? goNormalAssignTab : undefined} | |||
| lanePanelPrefs={lanePanelPrefs} | |||
| onLanePanelPrefsChange={onLanePanelPrefsChange} | |||
| /> | |||
| ) : ( | |||
| <WorkbenchGoodPickExecutionDetail | |||
| @@ -29,6 +29,10 @@ import { | |||
| } from "@/app/api/doworkbench/actions"; | |||
| import FinishedGoodCartonDashboardTab from "../FinishedGoodSearch/FinishedGoodCartonDashboardTab"; | |||
| import TruckRoutingSummaryTabWorkbench from "./TruckRoutingSummaryTabWorkbench"; | |||
| import { | |||
| DEFAULT_WORKBENCH_LANE_PANEL_PREFS, | |||
| type WorkbenchLanePanelPrefs, | |||
| } from "./workbenchLanePanelPrefs"; | |||
| const ALLOWED_WORKBENCH_TABS = new Set([0, 1, 2, 3, 4, 5, 6]); | |||
| @@ -72,6 +76,9 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||
| : null; | |||
| const [tab, setTab] = React.useState<number>(defaultTabIndex); | |||
| const [lanePanelPrefs, setLanePanelPrefs] = React.useState<WorkbenchLanePanelPrefs>( | |||
| DEFAULT_WORKBENCH_LANE_PANEL_PREFS, | |||
| ); | |||
| const [a4Printer, setA4Printer] = React.useState<PrinterCombo | null>(null); | |||
| const [labelPrinter, setLabelPrinter] = React.useState<PrinterCombo | null>(null); | |||
| const [releasedOrderCount, setReleasedOrderCount] = React.useState(0); | |||
| @@ -356,10 +363,18 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||
| </Tabs> | |||
| <TabPanel value={tab} index={0}> | |||
| <DoWorkbenchPickShell laneMode="normal" /> | |||
| <DoWorkbenchPickShell | |||
| laneMode="normal" | |||
| lanePanelPrefs={lanePanelPrefs} | |||
| onLanePanelPrefsChange={setLanePanelPrefs} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={1}> | |||
| <DoWorkbenchPickShell laneMode="etra" /> | |||
| <DoWorkbenchPickShell | |||
| laneMode="etra" | |||
| lanePanelPrefs={lanePanelPrefs} | |||
| onLanePanelPrefsChange={setLanePanelPrefs} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={2}> | |||
| <GoodPickExecutionWorkbenchRecord | |||
| @@ -18,6 +18,10 @@ import { | |||
| import Swal from "sweetalert2"; | |||
| import dayjs from "dayjs"; | |||
| import ReleasedDoPickOrderSelectModal from "@/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal"; | |||
| import { | |||
| DEFAULT_WORKBENCH_LANE_PANEL_PREFS, | |||
| type WorkbenchLanePanelPrefs, | |||
| } from "./workbenchLanePanelPrefs"; | |||
| interface Props { | |||
| onPickOrderAssigned?: () => void; | |||
| @@ -27,6 +31,9 @@ interface Props { | |||
| 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 }; | |||
| @@ -38,6 +45,8 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||
| initialReleaseType = "batch", | |||
| etraOnly = false, | |||
| onRequestNormalLaneTab, | |||
| lanePanelPrefs: lanePanelPrefsProp, | |||
| onLanePanelPrefsChange, | |||
| }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| @@ -54,9 +63,21 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||
| const [isAssigning, setIsAssigning] = useState(false); | |||
| const [isDefaultTruck, setIsDefaultTruck] = useState(false); | |||
| const [beforeTodayTruckXCount, setBeforeTodayTruckXCount] = useState(0); | |||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||
| const [releaseType, setReleaseType] = useState<string>(initialReleaseType); | |||
| const [ticketFloor, setTicketFloor] = useState<"2/F" | "4/F">("2/F"); | |||
| 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); | |||
| @@ -364,7 +385,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||
| <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) => setSelectedDate(e.target.value)}> | |||
| <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> | |||
| @@ -376,7 +397,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||
| <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) => setReleaseType(e.target.value)}> | |||
| <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> | |||
| @@ -385,7 +406,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||
| <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) => setTicketFloor(e.target.value as "2/F" | "4/F")}> | |||
| <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> | |||
| @@ -0,0 +1,23 @@ | |||
| export type WorkbenchLaneDateKey = "today" | "tomorrow" | "dayAfterTomorrow"; | |||
| export type WorkbenchLaneFloor = "2/F" | "4/F"; | |||
| /** Tab 0/1 lane assignment filters — lifted to DoWorkbenchTabs so they survive tab switches. */ | |||
| export type WorkbenchLanePanelPrefs = { | |||
| selectedDate: WorkbenchLaneDateKey; | |||
| ticketFloor: WorkbenchLaneFloor; | |||
| releaseType: string; | |||
| }; | |||
| export const DEFAULT_WORKBENCH_LANE_PANEL_PREFS: WorkbenchLanePanelPrefs = { | |||
| selectedDate: "today", | |||
| ticketFloor: "2/F", | |||
| releaseType: "batch", | |||
| }; | |||
| export function isWorkbenchLaneDateKey(v: string): v is WorkbenchLaneDateKey { | |||
| return v === "today" || v === "tomorrow" || v === "dayAfterTomorrow"; | |||
| } | |||
| export function isWorkbenchLaneFloor(v: string): v is WorkbenchLaneFloor { | |||
| return v === "2/F" || v === "4/F"; | |||
| } | |||
| @@ -15,6 +15,8 @@ | |||
| "Remove": "Remove", | |||
| "Create Stock Take (Select Sections)": "Create stock take (select sections)", | |||
| "Select stock take sections to create hint": "Choose warehouse stock take sections to start a new round (selected sections share one new round id in this batch).", | |||
| "Stock take round name": "Stock take round name", | |||
| "Stock take round name placeholder": "Optional, e.g. May full-site stock take", | |||
| "Select sections placeholder": "Select one or more", | |||
| "Select all sections": "Select all sections", | |||
| "Clear selection": "Clear selection", | |||
| @@ -131,6 +131,8 @@ | |||
| "Create Stock Take for All Sections": "為所有區域創建盤點", | |||
| "Create Stock Take (Select Sections)": "建立盤點(選擇區域)", | |||
| "Select stock take sections to create hint": "請選擇要建立新盤點輪次的盤點區域(同一批次將共用同一輪次編號)。", | |||
| "Stock take round name": "盤點輪次名稱", | |||
| "Stock take round name placeholder": "選填,例如:五月全廠盤點", | |||
| "Select sections placeholder": "可多選", | |||
| "Select all sections": "全選區域", | |||
| "Clear selection": "清除選取", | |||