| @@ -10,18 +10,25 @@ import { | |||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import WorkbenchFloorLanePanel from "./WorkbenchFloorLanePanel"; | import WorkbenchFloorLanePanel from "./WorkbenchFloorLanePanel"; | ||||
| import WorkbenchGoodPickExecutionDetail from "./WorkbenchGoodPickExecutionDetail"; | import WorkbenchGoodPickExecutionDetail from "./WorkbenchGoodPickExecutionDetail"; | ||||
| import type { WorkbenchLanePanelPrefs } from "./workbenchLanePanelPrefs"; | |||||
| export type DoWorkbenchPickShellLaneMode = "normal" | "etra"; | export type DoWorkbenchPickShellLaneMode = "normal" | "etra"; | ||||
| type DoWorkbenchPickShellProps = { | type DoWorkbenchPickShellProps = { | ||||
| /** Tab 0: normal 2F/4F lane grid; tab 1: Etra-only lane grid */ | /** Tab 0: normal 2F/4F lane grid; tab 1: Etra-only lane grid */ | ||||
| laneMode?: DoWorkbenchPickShellLaneMode; | laneMode?: DoWorkbenchPickShellLaneMode; | ||||
| lanePanelPrefs: WorkbenchLanePanelPrefs; | |||||
| onLanePanelPrefsChange: (prefs: WorkbenchLanePanelPrefs) => void; | |||||
| }; | }; | ||||
| /** | /** | ||||
| * FG workbench: 未指派顯示樓層/車線指派;已指派顯示揀貨明細(workbench API)。 | * 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 { | const { data: session, status } = useSession() as { | ||||
| data: SessionWithTokens | null; | data: SessionWithTokens | null; | ||||
| status: "loading" | "authenticated" | "unauthenticated"; | status: "loading" | "authenticated" | "unauthenticated"; | ||||
| @@ -104,6 +111,8 @@ const DoWorkbenchPickShell: React.FC<DoWorkbenchPickShellProps> = ({ laneMode = | |||||
| onPickOrderAssigned={() => void refreshWorkbenchView()} | onPickOrderAssigned={() => void refreshWorkbenchView()} | ||||
| etraOnly={laneMode === "etra"} | etraOnly={laneMode === "etra"} | ||||
| onRequestNormalLaneTab={laneMode === "etra" ? goNormalAssignTab : undefined} | onRequestNormalLaneTab={laneMode === "etra" ? goNormalAssignTab : undefined} | ||||
| lanePanelPrefs={lanePanelPrefs} | |||||
| onLanePanelPrefsChange={onLanePanelPrefsChange} | |||||
| /> | /> | ||||
| ) : ( | ) : ( | ||||
| <WorkbenchGoodPickExecutionDetail | <WorkbenchGoodPickExecutionDetail | ||||
| @@ -29,6 +29,10 @@ import { | |||||
| } from "@/app/api/doworkbench/actions"; | } from "@/app/api/doworkbench/actions"; | ||||
| import FinishedGoodCartonDashboardTab from "../FinishedGoodSearch/FinishedGoodCartonDashboardTab"; | import FinishedGoodCartonDashboardTab from "../FinishedGoodSearch/FinishedGoodCartonDashboardTab"; | ||||
| import TruckRoutingSummaryTabWorkbench from "./TruckRoutingSummaryTabWorkbench"; | 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]); | 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; | : null; | ||||
| const [tab, setTab] = React.useState<number>(defaultTabIndex); | 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 [a4Printer, setA4Printer] = React.useState<PrinterCombo | null>(null); | ||||
| const [labelPrinter, setLabelPrinter] = React.useState<PrinterCombo | null>(null); | const [labelPrinter, setLabelPrinter] = React.useState<PrinterCombo | null>(null); | ||||
| const [releasedOrderCount, setReleasedOrderCount] = React.useState(0); | const [releasedOrderCount, setReleasedOrderCount] = React.useState(0); | ||||
| @@ -356,10 +363,18 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||||
| </Tabs> | </Tabs> | ||||
| <TabPanel value={tab} index={0}> | <TabPanel value={tab} index={0}> | ||||
| <DoWorkbenchPickShell laneMode="normal" /> | |||||
| <DoWorkbenchPickShell | |||||
| laneMode="normal" | |||||
| lanePanelPrefs={lanePanelPrefs} | |||||
| onLanePanelPrefsChange={setLanePanelPrefs} | |||||
| /> | |||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tab} index={1}> | <TabPanel value={tab} index={1}> | ||||
| <DoWorkbenchPickShell laneMode="etra" /> | |||||
| <DoWorkbenchPickShell | |||||
| laneMode="etra" | |||||
| lanePanelPrefs={lanePanelPrefs} | |||||
| onLanePanelPrefsChange={setLanePanelPrefs} | |||||
| /> | |||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tab} index={2}> | <TabPanel value={tab} index={2}> | ||||
| <GoodPickExecutionWorkbenchRecord | <GoodPickExecutionWorkbenchRecord | ||||
| @@ -18,6 +18,10 @@ import { | |||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import ReleasedDoPickOrderSelectModal from "@/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal"; | import ReleasedDoPickOrderSelectModal from "@/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal"; | ||||
| import { | |||||
| DEFAULT_WORKBENCH_LANE_PANEL_PREFS, | |||||
| type WorkbenchLanePanelPrefs, | |||||
| } from "./workbenchLanePanelPrefs"; | |||||
| interface Props { | interface Props { | ||||
| onPickOrderAssigned?: () => void; | onPickOrderAssigned?: () => void; | ||||
| @@ -27,6 +31,9 @@ interface Props { | |||||
| etraOnly?: boolean; | etraOnly?: boolean; | ||||
| /** With [etraOnly], navigates to normal assign tab (tab 0). */ | /** With [etraOnly], navigates to normal assign tab (tab 0). */ | ||||
| onRequestNormalLaneTab?: () => void; | 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 LaneSlot4F = { truckDepartureTime: string; lane: LaneBtn }; | ||||
| @@ -38,6 +45,8 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| initialReleaseType = "batch", | initialReleaseType = "batch", | ||||
| etraOnly = false, | etraOnly = false, | ||||
| onRequestNormalLaneTab, | onRequestNormalLaneTab, | ||||
| lanePanelPrefs: lanePanelPrefsProp, | |||||
| onLanePanelPrefsChange, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -54,9 +63,21 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| const [isAssigning, setIsAssigning] = useState(false); | const [isAssigning, setIsAssigning] = useState(false); | ||||
| const [isDefaultTruck, setIsDefaultTruck] = useState(false); | const [isDefaultTruck, setIsDefaultTruck] = useState(false); | ||||
| const [beforeTodayTruckXCount, setBeforeTodayTruckXCount] = useState(0); | 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 [isExtraView, setisExtraView] = useState(false); | ||||
| const [etraGroups, setEtraGroups] = useState<WorkbenchEtraShopLaneGroup[]>([]); | const [etraGroups, setEtraGroups] = useState<WorkbenchEtraShopLaneGroup[]>([]); | ||||
| const [isLoadingEtra, setIsLoadingEtra] = useState(false); | const [isLoadingEtra, setIsLoadingEtra] = useState(false); | ||||
| @@ -364,7 +385,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| <Box sx={{ maxWidth: 300 }}> | <Box sx={{ maxWidth: 300 }}> | ||||
| <FormControl fullWidth size="small"> | <FormControl fullWidth size="small"> | ||||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | <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="today">{t("Today")} ({getDateLabel(0)})</MenuItem> | ||||
| <MenuItem value="tomorrow">{t("Tomorrow")} ({getDateLabel(1)})</MenuItem> | <MenuItem value="tomorrow">{t("Tomorrow")} ({getDateLabel(1)})</MenuItem> | ||||
| <MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</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 }}> | <Box sx={{ minWidth: 140, maxWidth: 300 }}> | ||||
| <FormControl fullWidth size="small"> | <FormControl fullWidth size="small"> | ||||
| <InputLabel id="release-type-select-label">{t("Release Type")}</InputLabel> | <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="batch">{t("Batch")}</MenuItem> | ||||
| <MenuItem value="single">{t("Single")}</MenuItem> | <MenuItem value="single">{t("Single")}</MenuItem> | ||||
| </Select> | </Select> | ||||
| @@ -385,7 +406,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| <Box sx={{ minWidth: 120, maxWidth: 200 }}> | <Box sx={{ minWidth: 120, maxWidth: 200 }}> | ||||
| <FormControl fullWidth size="small"> | <FormControl fullWidth size="small"> | ||||
| <InputLabel id="ticket-floor-select-label">{t("Floor ticket")}</InputLabel> | <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="2/F">{t("2F ticket")}</MenuItem> | ||||
| <MenuItem value="4/F">{t("4F ticket")}</MenuItem> | <MenuItem value="4/F">{t("4F ticket")}</MenuItem> | ||||
| </Select> | </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", | "Remove": "Remove", | ||||
| "Create Stock Take (Select Sections)": "Create stock take (select sections)", | "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).", | "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 sections placeholder": "Select one or more", | ||||
| "Select all sections": "Select all sections", | "Select all sections": "Select all sections", | ||||
| "Clear selection": "Clear selection", | "Clear selection": "Clear selection", | ||||
| @@ -131,6 +131,8 @@ | |||||
| "Create Stock Take for All Sections": "為所有區域創建盤點", | "Create Stock Take for All Sections": "為所有區域創建盤點", | ||||
| "Create Stock Take (Select Sections)": "建立盤點(選擇區域)", | "Create Stock Take (Select Sections)": "建立盤點(選擇區域)", | ||||
| "Select stock take sections to create hint": "請選擇要建立新盤點輪次的盤點區域(同一批次將共用同一輪次編號)。", | "Select stock take sections to create hint": "請選擇要建立新盤點輪次的盤點區域(同一批次將共用同一輪次編號)。", | ||||
| "Stock take round name": "盤點輪次名稱", | |||||
| "Stock take round name placeholder": "選填,例如:五月全廠盤點", | |||||
| "Select sections placeholder": "可多選", | "Select sections placeholder": "可多選", | ||||
| "Select all sections": "全選區域", | "Select all sections": "全選區域", | ||||
| "Clear selection": "清除選取", | "Clear selection": "清除選取", | ||||