Workbench, delivery search, reports, and lot suggestion now match new dbdebrand
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import React, { useCallback, useMemo, useState } from "react"; | import React, { useCallback, useMemo, useState } from "react"; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -37,8 +38,8 @@ const STAFF_PERF_STORE_FILTER_OPTIONS: { | |||||
| label: string; | label: string; | ||||
| }[] = [ | }[] = [ | ||||
| { value: "all", label: "全部" }, | { value: "all", label: "全部" }, | ||||
| { value: "2/F", label: "2/F" }, | |||||
| { value: "4/F", label: "4/F" }, | |||||
| { value: FLOOR_A, label: FLOOR_A }, | |||||
| { value: FLOOR_B, label: FLOOR_B }, | |||||
| { value: "null_only", label: "車線-X" }, | { value: "null_only", label: "車線-X" }, | ||||
| ]; | ]; | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; | import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; | ||||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | import { NEXT_PUBLIC_API_URL } from "@/config/api"; | ||||
| @@ -577,7 +578,7 @@ export async function fetchPlannedOutputByDateAndItem( | |||||
| } | } | ||||
| /** Warehouse / lane filter for staff delivery performance chart (delivery_order_pick_order.store_id). */ | /** Warehouse / lane filter for staff delivery performance chart (delivery_order_pick_order.store_id). */ | ||||
| export type StaffDeliveryPerformanceStoreFilter = "all" | "2/F" | "4/F" | "null_only"; | |||||
| export type StaffDeliveryPerformanceStoreFilter = "all" | typeof FLOOR_A | typeof FLOOR_B | "null_only"; | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 61 | v1.0.0 | 2026-08-10 */ | /** FP-MTMS Version Checklist | Functions Ref. No. 61 | v1.0.0 | 2026-08-10 */ | ||||
| export async function fetchStaffDeliveryPerformance( | export async function fetchStaffDeliveryPerformance( | ||||
| @@ -591,8 +592,8 @@ export async function fetchStaffDeliveryPerformance( | |||||
| if (endDate) p.set("endDate", endDate); | if (endDate) p.set("endDate", endDate); | ||||
| (staffNos ?? []).forEach((no) => p.append("staffNo", no)); | (staffNos ?? []).forEach((no) => p.append("staffNo", no)); | ||||
| if (storeFilter === "null_only") p.set("storeIdNull", "true"); | if (storeFilter === "null_only") p.set("storeIdNull", "true"); | ||||
| else if (storeFilter === "2/F") p.set("storeId", "2/F"); | |||||
| else if (storeFilter === "4/F") p.set("storeId", "4/F"); | |||||
| else if (storeFilter === FLOOR_A) p.set("storeId", FLOOR_A); | |||||
| else if (storeFilter === FLOOR_B) p.set("storeId", FLOOR_B); | |||||
| const q = p.toString(); | const q = p.toString(); | ||||
| const res = await clientAuthFetch( | const res = await clientAuthFetch( | ||||
| q ? `${BASE}/staff-delivery-performance?${q}` : `${BASE}/staff-delivery-performance` | q ? `${BASE}/staff-delivery-performance?${q}` : `${BASE}/staff-delivery-performance` | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use server"; | "use server"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| @@ -85,7 +86,7 @@ export interface ReleaseDoResponse { | |||||
| export interface AssignByStoreRequest { | export interface AssignByStoreRequest { | ||||
| storeId: string; // "2/F" or "4/F" | |||||
| storeId: string; // FLOOR_A or FLOOR_B | |||||
| assignTo: number; | assignTo: number; | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use server"; | "use server"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
| @@ -244,7 +245,7 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelection( | |||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| releaseType?: string, | releaseType?: string, | ||||
| /** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| /** Optional `${FLOOR_A_KEY}`/`${FLOOR_B_KEY}`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| floor?: string | floor?: string | ||||
| ): Promise<ReleasedDoPickOrderListItem[]> { | ): Promise<ReleasedDoPickOrderListItem[]> { | ||||
| const params = new URLSearchParams(); | const params = new URLSearchParams(); | ||||
| @@ -266,7 +267,7 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday( | |||||
| truck?: string, | truck?: string, | ||||
| requiredDeliveryDate?: string, | requiredDeliveryDate?: string, | ||||
| releaseType?: string, | releaseType?: string, | ||||
| /** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| /** Optional `${FLOOR_A_KEY}`/`${FLOOR_B_KEY}`: Truck X split by DO supplier preferred floor (storeId stays null). */ | |||||
| floor?: string | floor?: string | ||||
| ): Promise<ReleasedDoPickOrderListItem[]> { | ): Promise<ReleasedDoPickOrderListItem[]> { | ||||
| const params = new URLSearchParams(); | const params = new URLSearchParams(); | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use server"; | "use server"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import { cache } from 'react'; | import { cache } from 'react'; | ||||
| import { Pageable, ServerFetchError, serverFetchBlob, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | import { Pageable, ServerFetchError, serverFetchBlob, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { JobOrder, JoStatus, Machine, Operator } from "."; | import { JobOrder, JoStatus, Machine, Operator } from "."; | ||||
| @@ -133,7 +134,7 @@ export interface PrintPickRecordRequest{ | |||||
| pickOrderId: number; | pickOrderId: number; | ||||
| printerId: number; | printerId: number; | ||||
| printQty: number; | printQty: number; | ||||
| floor?: "2F" | "3F" | "4F" | "ALL"; | |||||
| floor?: typeof FLOOR_A_KEY | typeof FLOOR_D_KEY | typeof FLOOR_B_KEY | "ALL"; | |||||
| plasticBoxCartonQty?: number; | plasticBoxCartonQty?: number; | ||||
| plasticBoxCartonQty2f?: number; | plasticBoxCartonQty2f?: number; | ||||
| plasticBoxCartonQty3f?: number; | plasticBoxCartonQty3f?: number; | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use server"; | "use server"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| @@ -333,7 +334,7 @@ export interface DoPickOrderDetail { | |||||
| } | } | ||||
| export interface AutoAssignReleaseByStoreRequest { | export interface AutoAssignReleaseByStoreRequest { | ||||
| userId: number; | userId: number; | ||||
| storeId: string; // "2/F" | "4/F" | |||||
| storeId: string; // FLOOR_A | FLOOR_B | |||||
| } | } | ||||
| export interface UpdateDoPickOrderHideStatusRequest { | export interface UpdateDoPickOrderHideStatusRequest { | ||||
| id: number; | id: number; | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { SETTING_SUPPLIERS_A, SETTING_SUPPLIERS_B } from "@/config/demoFloors"; | |||||
| /** `settings.name`:逗號分隔 supplier **code**(須與後端讀取邏輯一致)。 */ | /** `settings.name`:逗號分隔 supplier **code**(須與後端讀取邏輯一致)。 */ | ||||
| export const SETTING_DO_FLOOR_SUPPLIERS_2F = "DO.floor.suppliers.2F"; | |||||
| export const SETTING_DO_FLOOR_SUPPLIERS_4F = "DO.floor.suppliers.4F"; | |||||
| export const SETTING_DO_FLOOR_SUPPLIERS_2F = SETTING_SUPPLIERS_A; | |||||
| export const SETTING_DO_FLOOR_SUPPLIERS_4F = SETTING_SUPPLIERS_B; | |||||
| export const SETTING_DO_FLOOR_CATEGORY = "DO_FLOOR"; | export const SETTING_DO_FLOOR_CATEGORY = "DO_FLOOR"; | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import dayjs, { ConfigType, Dayjs } from "dayjs"; | import dayjs, { ConfigType, Dayjs } from "dayjs"; | ||||
| import { Uom } from "../api/settings/uom"; | import { Uom } from "../api/settings/uom"; | ||||
| import { | import { | ||||
| @@ -249,7 +250,7 @@ export const normalizeStoreId = (storeId: string | number | String | Number | nu | |||||
| const storeIdStr = typeof storeId === 'string' || storeId instanceof String | const storeIdStr = typeof storeId === 'string' || storeId instanceof String | ||||
| ? String(storeId) | ? String(storeId) | ||||
| : String(storeId); | : String(storeId); | ||||
| if (storeIdStr === "2" || storeIdStr === "2F") return "2F"; | |||||
| if (storeIdStr === "4" || storeIdStr === "4F") return "4F"; | |||||
| if (storeIdStr === "2" || storeIdStr === FLOOR_A_KEY) return FLOOR_A_KEY; | |||||
| if (storeIdStr === "4" || storeIdStr === FLOOR_B_KEY) return FLOOR_B_KEY; | |||||
| return storeIdStr; | return storeIdStr; | ||||
| }; | }; | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; | import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -449,8 +450,8 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| displayEmpty | displayEmpty | ||||
| > | > | ||||
| <MenuItem value="">{t("All Stores")}</MenuItem> | <MenuItem value="">{t("All Stores")}</MenuItem> | ||||
| <MenuItem value="2/F">2/F</MenuItem> | |||||
| <MenuItem value="4/F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{FLOOR_B}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| @@ -535,7 +536,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <Chip | <Chip | ||||
| label={row.storeId || '-'} | label={row.storeId || '-'} | ||||
| size="small" | size="small" | ||||
| color={row.storeId === '2/F' ? 'primary' : 'secondary'} | |||||
| color={row.storeId === FLOOR_A ? 'primary' : 'secondary'} | |||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | <TableCell> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; | import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import EditOutlined from "@mui/icons-material/EditOutlined"; | import EditOutlined from "@mui/icons-material/EditOutlined"; | ||||
| @@ -57,7 +58,7 @@ function formatBracketList(codesCsv: string): string { | |||||
| return `[${n.split(",").join(", ")}]`; | return `[${n.split(",").join(", ")}]`; | ||||
| } | } | ||||
| type EditFloor = "2F" | "4F"; | |||||
| type EditFloor = typeof FLOOR_A_KEY | typeof FLOOR_B_KEY; | |||||
| type FloorRow = { code: string; name: string }; | type FloorRow = { code: string; name: string }; | ||||
| @@ -97,7 +98,7 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| const [codes4F, setCodes4F] = useState(""); | const [codes4F, setCodes4F] = useState(""); | ||||
| const [editOpen, setEditOpen] = useState(false); | const [editOpen, setEditOpen] = useState(false); | ||||
| const [editFloor, setEditFloor] = useState<EditFloor>("2F"); | |||||
| const [editFloor, setEditFloor] = useState<EditFloor>(FLOOR_A_KEY); | |||||
| const [dialogSaving, setDialogSaving] = useState(false); | const [dialogSaving, setDialogSaving] = useState(false); | ||||
| const [comboLoading, setComboLoading] = useState(false); | const [comboLoading, setComboLoading] = useState(false); | ||||
| const [supplierCombo, setSupplierCombo] = useState<ShopComboRow[] | null>(null); | const [supplierCombo, setSupplierCombo] = useState<ShopComboRow[] | null>(null); | ||||
| @@ -130,8 +131,8 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| const display2F = useMemo(() => formatBracketList(codes2F), [codes2F]); | const display2F = useMemo(() => formatBracketList(codes2F), [codes2F]); | ||||
| const display4F = useMemo(() => formatBracketList(codes4F), [codes4F]); | const display4F = useMemo(() => formatBracketList(codes4F), [codes4F]); | ||||
| const currentDraftRows = editFloor === "2F" ? draftRows2F : draftRows4F; | |||||
| const setCurrentDraftRows = editFloor === "2F" ? setDraftRows2F : setDraftRows4F; | |||||
| const currentDraftRows = editFloor === FLOOR_A_KEY ? draftRows2F : draftRows4F; | |||||
| const setCurrentDraftRows = editFloor === FLOOR_A_KEY ? setDraftRows2F : setDraftRows4F; | |||||
| const openEdit = async (floor: EditFloor) => { | const openEdit = async (floor: EditFloor) => { | ||||
| setEditFloor(floor); | setEditFloor(floor); | ||||
| @@ -172,12 +173,12 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| setError(null); | setError(null); | ||||
| setSuccess(null); | setSuccess(null); | ||||
| try { | try { | ||||
| const rows = editFloor === "2F" ? draftRows2F : draftRows4F; | |||||
| const rows = editFloor === FLOOR_A_KEY ? draftRows2F : draftRows4F; | |||||
| const normalized = normalizeCodesCsv(floorRowsToCsv(rows)); | const normalized = normalizeCodesCsv(floorRowsToCsv(rows)); | ||||
| const key = | const key = | ||||
| editFloor === "2F" ? SETTING_DO_FLOOR_SUPPLIERS_2F : SETTING_DO_FLOOR_SUPPLIERS_4F; | |||||
| editFloor === FLOOR_A_KEY ? SETTING_DO_FLOOR_SUPPLIERS_2F : SETTING_DO_FLOOR_SUPPLIERS_4F; | |||||
| await postSettingClient(key, normalized); | await postSettingClient(key, normalized); | ||||
| if (editFloor === "2F") setCodes2F(normalized); | |||||
| if (editFloor === FLOOR_A_KEY) setCodes2F(normalized); | |||||
| else setCodes4F(normalized); | else setCodes4F(normalized); | ||||
| setSuccess(t("Saved")); | setSuccess(t("Saved")); | ||||
| setEditOpen(false); | setEditOpen(false); | ||||
| @@ -237,7 +238,7 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| return; | return; | ||||
| } | } | ||||
| const canonical = hit.code.trim(); | const canonical = hit.code.trim(); | ||||
| const otherRows = editFloor === "2F" ? draftRows4F : draftRows2F; | |||||
| const otherRows = editFloor === FLOOR_A_KEY ? draftRows4F : draftRows2F; | |||||
| if (currentDraftRows.some((r) => r.code.toLowerCase() === canonical.toLowerCase())) { | if (currentDraftRows.some((r) => r.code.toLowerCase() === canonical.toLowerCase())) { | ||||
| setAddError(t("Duplicate in floor")); | setAddError(t("Duplicate in floor")); | ||||
| return; | return; | ||||
| @@ -286,7 +287,7 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| }} | }} | ||||
| > | > | ||||
| <Typography component="span" variant="subtitle1" sx={{ minWidth: 120, fontWeight: 600 }}> | <Typography component="span" variant="subtitle1" sx={{ minWidth: 120, fontWeight: 600 }}> | ||||
| {t("2F supplier")} | |||||
| {t(FLOOR_A_KEY + " supplier")} | |||||
| </Typography> | </Typography> | ||||
| <Typography | <Typography | ||||
| component="span" | component="span" | ||||
| @@ -296,10 +297,10 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| {display2F} | {display2F} | ||||
| </Typography> | </Typography> | ||||
| <IconButton | <IconButton | ||||
| aria-label={t("Edit 2F")} | |||||
| aria-label={t("Edit " + FLOOR_A_KEY)} | |||||
| color="primary" | color="primary" | ||||
| edge="end" | edge="end" | ||||
| onClick={() => void openEdit("2F")} | |||||
| onClick={() => void openEdit(FLOOR_A_KEY)} | |||||
| size="small" | size="small" | ||||
| > | > | ||||
| <EditOutlined /> | <EditOutlined /> | ||||
| @@ -319,7 +320,7 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| }} | }} | ||||
| > | > | ||||
| <Typography component="span" variant="subtitle1" sx={{ minWidth: 120, fontWeight: 600 }}> | <Typography component="span" variant="subtitle1" sx={{ minWidth: 120, fontWeight: 600 }}> | ||||
| {t("4F supplier")} | |||||
| {t(FLOOR_B_KEY + " supplier")} | |||||
| </Typography> | </Typography> | ||||
| <Typography | <Typography | ||||
| component="span" | component="span" | ||||
| @@ -329,10 +330,10 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| {display4F} | {display4F} | ||||
| </Typography> | </Typography> | ||||
| <IconButton | <IconButton | ||||
| aria-label={t("Edit 4F")} | |||||
| aria-label={t("Edit " + FLOOR_B_KEY)} | |||||
| color="primary" | color="primary" | ||||
| edge="end" | edge="end" | ||||
| onClick={() => void openEdit("4F")} | |||||
| onClick={() => void openEdit(FLOOR_B_KEY)} | |||||
| size="small" | size="small" | ||||
| > | > | ||||
| <EditOutlined /> | <EditOutlined /> | ||||
| @@ -361,8 +362,8 @@ const DeliveryOrderFloorSettings: React.FC = () => { | |||||
| onChange={onFloorSelectChange} | onChange={onFloorSelectChange} | ||||
| disabled={dialogSaving || comboLoading} | disabled={dialogSaving || comboLoading} | ||||
| > | > | ||||
| <MenuItem value="2F">2/F</MenuItem> | |||||
| <MenuItem value="4F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A_KEY}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_B_KEY}>{FLOOR_B}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Button | <Button | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import type { DoDetail as DoDetailType } from "@/app/api/do/actions"; | import type { DoDetail as DoDetailType } from "@/app/api/do/actions"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -203,20 +204,20 @@ const DoDetail: React.FC<Props> = ({ | |||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => handleAssignByStore("2/F")} | |||||
| onClick={() => handleAssignByStore(FLOOR_A)} | |||||
| disabled={isAssigning} | disabled={isAssigning} | ||||
| sx={{ minWidth: 120 }} | sx={{ minWidth: 120 }} | ||||
| > | > | ||||
| {t("Assign 2/F")} | |||||
| {t("Assign " + FLOOR_A)} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="secondary" | color="secondary" | ||||
| onClick={() => handleAssignByStore("4/F")} | |||||
| onClick={() => handleAssignByStore(FLOOR_B)} | |||||
| disabled={isAssigning} | disabled={isAssigning} | ||||
| sx={{ minWidth: 120 }} | sx={{ minWidth: 120 }} | ||||
| > | > | ||||
| {t("Assign 4/F")} | |||||
| {t("Assign " + FLOOR_B)} | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| @@ -224,20 +225,20 @@ const DoDetail: React.FC<Props> = ({ | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => handleReleaseByStore("2/F")} | |||||
| onClick={() => handleReleaseByStore(FLOOR_A)} | |||||
| disabled={isAssigning} | disabled={isAssigning} | ||||
| sx={{ minWidth: 120 }} | sx={{ minWidth: 120 }} | ||||
| > | > | ||||
| {t("Release 2/F")} | |||||
| {t("Release " + FLOOR_A)} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| color="secondary" | color="secondary" | ||||
| onClick={() => handleReleaseByStore("4/F")} | |||||
| onClick={() => handleReleaseByStore(FLOOR_B)} | |||||
| disabled={isAssigning} | disabled={isAssigning} | ||||
| sx={{ minWidth: 120 }} | sx={{ minWidth: 120 }} | ||||
| > | > | ||||
| {t("Release 4/F")} | |||||
| {t("Release " + FLOOR_B)} | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { DoResult } from "@/app/api/do"; | import { DoResult } from "@/app/api/do"; | ||||
| import { | import { | ||||
| DoSearchAll, | DoSearchAll, | ||||
| @@ -64,8 +65,8 @@ type Props = { | |||||
| }; | }; | ||||
| type SearchBoxInputs = Record<"code" | "status" | "estimatedArrivalDate" | "orderDate" | "supplierName" | "shopName" | "deliveryOrderLines" | "truckLanceCode" | "codeTo" | "statusTo" | "estimatedArrivalDateTo" | "orderDateTo" | "supplierNameTo" | "shopNameTo" | "deliveryOrderLinesTo" | "truckLanceCodeTo", string>; | type SearchBoxInputs = Record<"code" | "status" | "estimatedArrivalDate" | "orderDate" | "supplierName" | "shopName" | "deliveryOrderLines" | "truckLanceCode" | "codeTo" | "statusTo" | "estimatedArrivalDateTo" | "orderDateTo" | "supplierNameTo" | "shopNameTo" | "deliveryOrderLinesTo" | "truckLanceCodeTo", string>; | ||||
| type SearchParamNames = keyof SearchBoxInputs; | type SearchParamNames = keyof SearchBoxInputs; | ||||
| type DoSearchTab = "2F" | "4F" | "TRUCK_X" | "ETRA" | "REPLENISH"; | |||||
| type TabFilter = { floor: "2F" | "4F" | null; isExtra: boolean; forceTruckKeyword?: string }; | |||||
| type DoSearchTab = typeof FLOOR_A_KEY | typeof FLOOR_B_KEY | "TRUCK_X" | "ETRA" | "REPLENISH"; | |||||
| type TabFilter = { floor: typeof FLOOR_A_KEY | typeof FLOOR_B_KEY | null; isExtra: boolean; forceTruckKeyword?: string }; | |||||
| // put all this into a new component | // put all this into a new component | ||||
| // ConsoDoForm | // ConsoDoForm | ||||
| @@ -103,7 +104,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea | |||||
| const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]); | const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]); | ||||
| const [totalCount, setTotalCount] = useState(0); | const [totalCount, setTotalCount] = useState(0); | ||||
| const [isWorkbench, setIsWorkbench] = useState(false); | const [isWorkbench, setIsWorkbench] = useState(false); | ||||
| const [activeTab, setActiveTab] = useState<DoSearchTab>("2F"); | |||||
| const [activeTab, setActiveTab] = useState<DoSearchTab>(FLOOR_A_KEY); | |||||
| const [searchBoxResetKey, setSearchBoxResetKey] = useState(0); | const [searchBoxResetKey, setSearchBoxResetKey] = useState(0); | ||||
| const [pagingController, setPagingController] = useState({ | const [pagingController, setPagingController] = useState({ | ||||
| pageNum: 1, | pageNum: 1, | ||||
| @@ -326,10 +327,10 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea | |||||
| const resolveTabFilter = useCallback((tab: DoSearchTab): TabFilter => { | const resolveTabFilter = useCallback((tab: DoSearchTab): TabFilter => { | ||||
| switch (tab) { | switch (tab) { | ||||
| case "2F": | |||||
| return { floor: "2F", isExtra: false }; | |||||
| case "4F": | |||||
| return { floor: "4F", isExtra: false }; | |||||
| case FLOOR_A_KEY: | |||||
| return { floor: FLOOR_A_KEY, isExtra: false }; | |||||
| case FLOOR_B_KEY: | |||||
| return { floor: FLOOR_B_KEY, isExtra: false }; | |||||
| case "TRUCK_X": | case "TRUCK_X": | ||||
| return { floor: null, isExtra: false, forceTruckKeyword: "x" }; | return { floor: null, isExtra: false, forceTruckKeyword: "x" }; | ||||
| case "ETRA": | case "ETRA": | ||||
| @@ -802,8 +803,8 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea | |||||
| onChange={handleTabChange} | onChange={handleTabChange} | ||||
| variant="scrollable" | variant="scrollable" | ||||
| > | > | ||||
| <Tab value="2F" label="2/F" /> | |||||
| <Tab value="4F" label="4/F" /> | |||||
| <Tab value={FLOOR_A_KEY} label={FLOOR_A} /> | |||||
| <Tab value={FLOOR_B_KEY} label={FLOOR_B} /> | |||||
| <Tab value="TRUCK_X" label={t("Truck X")} /> | <Tab value="TRUCK_X" label={t("Truck X")} /> | ||||
| <Tab value="ETRA" label={t("Etra")} /> | <Tab value="ETRA" label={t("Etra")} /> | ||||
| <Tab value="REPLENISH" label={t("Replenishment")} /> | <Tab value="REPLENISH" label={t("Replenishment")} /> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { DoResult } from "@/app/api/do"; | import { DoResult } from "@/app/api/do"; | ||||
| import { DoSearchAll, DoSearchLiteResponse, fetchDoSearch, fetchAllDoSearch, fetchDoSearchList, releaseDo } from "@/app/api/do/actions"; | import { DoSearchAll, DoSearchLiteResponse, fetchDoSearch, fetchAllDoSearch, fetchDoSearchList, releaseDo } from "@/app/api/do/actions"; | ||||
| import { | import { | ||||
| @@ -150,8 +151,8 @@ const DoSearchWorkbench: React.FC<Props> = ({ | |||||
| paramName: "floor", | paramName: "floor", | ||||
| type: "select-labelled", | type: "select-labelled", | ||||
| options: [ | options: [ | ||||
| { label: "2F", value: "2F" }, | |||||
| { label: "4F", value: "4F" }, | |||||
| { label: FLOOR_A_KEY, value: FLOOR_A_KEY }, | |||||
| { label: FLOOR_B_KEY, value: FLOOR_B_KEY }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||
| import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material"; | import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material"; | ||||
| import DownloadIcon from "@mui/icons-material/Download"; | import DownloadIcon from "@mui/icons-material/Download"; | ||||
| @@ -114,7 +115,7 @@ const TruckRoutingSummaryTabWorkbench: React.FC = () => { | |||||
| <TextField | <TextField | ||||
| select | select | ||||
| fullWidth | fullWidth | ||||
| label={t("2/F or 4/F")} | |||||
| label={t(FLOOR_A + " or " + FLOOR_B)} | |||||
| value={storeId} | value={storeId} | ||||
| onChange={(e) => onStoreChange(e.target.value)} | onChange={(e) => onStoreChange(e.target.value)} | ||||
| > | > | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -41,8 +42,8 @@ type Props = { | |||||
| function floorLabel(storeId: string | null | undefined, t: (k: string) => string): string { | function floorLabel(storeId: string | null | undefined, t: (k: string) => string): string { | ||||
| const s = (storeId ?? "").trim().replace(/\//g, "").toUpperCase(); | const s = (storeId ?? "").trim().replace(/\//g, "").toUpperCase(); | ||||
| if (s === "2F") return "2/F"; | |||||
| if (s === "4F") return "4/F"; | |||||
| if (s === FLOOR_A_KEY) return FLOOR_A; | |||||
| if (s === FLOOR_B_KEY) return FLOOR_B; | |||||
| if (!s) return t("Truck X"); | if (!s) return t("Truck X"); | ||||
| return storeId ?? ""; | return storeId ?? ""; | ||||
| } | } | ||||
| @@ -58,7 +59,7 @@ function laneSecondary(c: WorkbenchMergeTicketCandidate, t: (k: string) => strin | |||||
| const dep = (c.truckDepartureTime ?? "").trim(); | const dep = (c.truckDepartureTime ?? "").trim(); | ||||
| const lane = (c.truckLanceCode ?? "").trim(); | const lane = (c.truckLanceCode ?? "").trim(); | ||||
| const floor = floorLabel(c.storeId, t); | const floor = floorLabel(c.storeId, t); | ||||
| const is4F = (c.storeId ?? "").trim().replace(/\//g, "").toUpperCase() === "4F"; | |||||
| const is4F = (c.storeId ?? "").trim().replace(/\//g, "").toUpperCase() === FLOOR_B_KEY; | |||||
| const seq = | const seq = | ||||
| is4F && c.loadingSequence != null | is4F && c.loadingSequence != null | ||||
| ? `${t("Loading sequence")} ${c.loadingSequence} · ` | ? `${t("Loading sequence")} ${c.loadingSequence} · ` | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel } from "@mui/material"; | import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel } from "@mui/material"; | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -53,7 +54,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const [selectedStore, setSelectedStore] = useState<string>("2/F"); | |||||
| const [selectedStore, setSelectedStore] = useState<string>(FLOOR_A); | |||||
| const [selectedTruck, setSelectedTruck] = useState<string>(""); | const [selectedTruck, setSelectedTruck] = useState<string>(""); | ||||
| const [modalOpen, setModalOpen] = useState(false); | const [modalOpen, setModalOpen] = useState(false); | ||||
| const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); | const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); | ||||
| @@ -92,7 +93,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| [ticketFloor], | [ticketFloor], | ||||
| ); | ); | ||||
| const defaultTruckCount = | const defaultTruckCount = | ||||
| (ticketFloor === "2/F" ? summary2F?.defaultTruckCount : summary4F?.defaultTruckCount) ?? 0; | |||||
| (ticketFloor === FLOOR_A ? summary2F?.defaultTruckCount : summary4F?.defaultTruckCount) ?? 0; | |||||
| const etraEnterInFlightRef = useRef(false); | const etraEnterInFlightRef = useRef(false); | ||||
| const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); | const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); | ||||
| @@ -167,8 +168,8 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| else if (selectedDate === "tomorrow") dateParam = dayjs().add(1, "day").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"); | else if (selectedDate === "dayAfterTomorrow") dateParam = dayjs().add(2, "day").format("YYYY-MM-DD"); | ||||
| const [s2, s4] = await Promise.all([ | const [s2, s4] = await Promise.all([ | ||||
| fetchWorkbenchStoreLaneSummary("2/F", dateParam, releaseType), | |||||
| fetchWorkbenchStoreLaneSummary("4/F", dateParam, releaseType), | |||||
| fetchWorkbenchStoreLaneSummary(FLOOR_A, dateParam, releaseType), | |||||
| fetchWorkbenchStoreLaneSummary(FLOOR_B, dateParam, releaseType), | |||||
| ]); | ]); | ||||
| setSummary2F(s2); | setSummary2F(s2); | ||||
| setSummary4F(s4); | setSummary4F(s4); | ||||
| @@ -217,8 +218,8 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| startFullTimer(); | startFullTimer(); | ||||
| try { | try { | ||||
| const [list2F, list4F] = await Promise.all([ | const [list2F, list4F] = await Promise.all([ | ||||
| fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, "2/F"), | |||||
| fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, "4/F"), | |||||
| fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, FLOOR_A), | |||||
| fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, FLOOR_B), | |||||
| ]); | ]); | ||||
| const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | ||||
| const map: Record<string, number> = {}; | const map: Record<string, number> = {}; | ||||
| @@ -441,8 +442,8 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| <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) => patchLanePanelPrefs({ ticketFloor: e.target.value as WorkbenchLanePanelPrefs["ticketFloor"] })}> | <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> | |||||
| <MenuItem value={FLOOR_A}>{t(FLOOR_A_KEY + " ticket")}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{t(FLOOR_B_KEY + " ticket")}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| </Box> | </Box> | ||||
| @@ -481,10 +482,10 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| {!inEtraUi ? ( | {!inEtraUi ? ( | ||||
| <Grid container spacing={2}> | <Grid container spacing={2}> | ||||
| {ticketFloor === "2/F" && ( | |||||
| {ticketFloor === FLOOR_A && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>2/F</Typography> | |||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{FLOOR_A}</Typography> | |||||
| <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | <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() : ( | {isLoadingSummary ? <Typography variant="caption">{t("Loading...")}</Typography> : !summary2F?.rows?.length ? renderNoEntry() : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| @@ -494,7 +495,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| <Typography variant="body2" sx={{ fontWeight: 600, minWidth: { sm: 60 } }}>{row.truckDepartureTime}</Typography> | <Typography variant="body2" sx={{ fontWeight: 600, minWidth: { sm: 60 } }}>{row.truckDepartureTime}</Typography> | ||||
| <Stack direction="row" flexWrap="wrap" sx={{ gap: 1 }}> | <Stack direction="row" flexWrap="wrap" sx={{ gap: 1 }}> | ||||
| {row.lanes.map((lane) => ( | {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)}> | |||||
| <Button key={`${row.truckDepartureTime}-${lane.truckLanceCode}`} variant="outlined" disabled={lane.unassigned === 0 || isAssigning} onClick={() => void handleLaneButtonClick(FLOOR_A, row.truckDepartureTime, lane.truckLanceCode, null, selectedDate, lane.unassigned, lane.total)}> | |||||
| {`${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`} | {`${lane.truckLanceCode} (${lane.unassigned}/${lane.total})`} | ||||
| </Button> | </Button> | ||||
| ))} | ))} | ||||
| @@ -509,10 +510,10 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| {ticketFloor === "4/F" && ( | |||||
| {ticketFloor === FLOOR_B && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>4/F</Typography> | |||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{FLOOR_B}</Typography> | |||||
| <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | ||||
| {isLoadingSummary ? <Typography variant="caption">{t("Loading...")}</Typography> : !truckGroups4F.length ? renderNoEntry() : ( | {isLoadingSummary ? <Typography variant="caption">{t("Loading...")}</Typography> : !truckGroups4F.length ? renderNoEntry() : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| @@ -524,7 +525,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| {slots.map((slot) => { | {slots.map((slot) => { | ||||
| const handlerName = (slot.lane.handlerName ?? "").trim(); | const handlerName = (slot.lane.handlerName ?? "").trim(); | ||||
| return ( | 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)}> | |||||
| <Button key={`${truckLanceCode}-${slot.sequenceIndex}-${slot.truckDepartureTime}`} variant="outlined" disabled={slot.lane.unassigned === 0 || isAssigning} onClick={() => void handleLaneButtonClick(FLOOR_B, 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}` : ""}`} | {`${t("Loading sequence n", { n: slot.lane.loadingSequence ?? slot.sequenceIndex })} (${slot.lane.unassigned}/${slot.lane.total})${handlerName ? ` ${handlerName}` : ""}`} | ||||
| </Button> | </Button> | ||||
| ); | ); | ||||
| @@ -585,21 +586,21 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| {ticketFloor === "2/F" && ( | |||||
| {ticketFloor === FLOOR_A && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>2/F</Typography> | |||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{FLOOR_A}</Typography> | |||||
| <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | ||||
| {truckCounts2F.length === 0 ? renderNoEntry() : ( | {truckCounts2F.length === 0 ? renderNoEntry() : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| {truckCounts2F.map(({ truck, count }) => ( | {truckCounts2F.map(({ truck, count }) => ( | ||||
| <Grid item xs={6} sm={4} md={3} key={`2F-${truck}`} sx={{ display: "flex" }}> | |||||
| <Grid item xs={6} sm={4} md={3} key={`${FLOOR_A_KEY}-${truck}`} sx={{ display: "flex" }}> | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| onClick={() => { | onClick={() => { | ||||
| clearModalEtraContext(); | clearModalEtraContext(); | ||||
| setIsDefaultTruck(false); | setIsDefaultTruck(false); | ||||
| setSelectedStore("2/F"); | |||||
| setSelectedStore(FLOOR_A); | |||||
| setSelectedTruck(truck); | setSelectedTruck(truck); | ||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| @@ -616,21 +617,21 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| {ticketFloor === "4/F" && ( | |||||
| {ticketFloor === FLOOR_B && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>4/F</Typography> | |||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{FLOOR_B}</Typography> | |||||
| <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, p: 1, backgroundColor: "#fafafa", flex: 1 }}> | ||||
| {truckCounts4F.length === 0 ? renderNoEntry() : ( | {truckCounts4F.length === 0 ? renderNoEntry() : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| {truckCounts4F.map(({ truck, count }) => ( | {truckCounts4F.map(({ truck, count }) => ( | ||||
| <Grid item xs={6} sm={4} md={3} key={`4F-${truck}`} sx={{ display: "flex" }}> | |||||
| <Grid item xs={6} sm={4} md={3} key={`${FLOOR_B_KEY}-${truck}`} sx={{ display: "flex" }}> | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| onClick={() => { | onClick={() => { | ||||
| clearModalEtraContext(); | clearModalEtraContext(); | ||||
| setIsDefaultTruck(false); | setIsDefaultTruck(false); | ||||
| setSelectedStore("4/F"); | |||||
| setSelectedStore(FLOOR_B); | |||||
| setSelectedTruck(truck); | setSelectedTruck(truck); | ||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| @@ -694,7 +695,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({ | |||||
| (lane.truckLanceCode ?? "").trim() === "車線-X" || | (lane.truckLanceCode ?? "").trim() === "車線-X" || | ||||
| (lane.truckLanceCode ?? "").trim() === "Truck X"; | (lane.truckLanceCode ?? "").trim() === "Truck X"; | ||||
| const floorKey = sid.replace(/\//g, "").toUpperCase(); | const floorKey = sid.replace(/\//g, "").toUpperCase(); | ||||
| const is4F = floorKey === "4F"; | |||||
| const is4F = floorKey === FLOOR_B_KEY; | |||||
| const labelCore = | const labelCore = | ||||
| isTruckX | isTruckX | ||||
| ? `${t("車線-X")}${sid ? ` · ${sid}` : ""} (${lane.unassigned}/${lane.total})` | ? `${t("車線-X")}${sid ? ` · ${sid}` : ""} (${lane.unassigned}/${lane.total})` | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, LABEL_A, LABEL_B } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -742,8 +743,8 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | |||||
| .toUpperCase() | .toUpperCase() | ||||
| .replace(/\s/g, ""); | .replace(/\s/g, ""); | ||||
| const floorKey = storeId.replace(/\//g, ""); | const floorKey = storeId.replace(/\//g, ""); | ||||
| if (floorKey === "2F") return "Label機 2F A+B"; | |||||
| if (floorKey === "4F") return "Label機 4F 乾貨 C, D"; | |||||
| if (floorKey === FLOOR_A_KEY) return LABEL_A; | |||||
| if (floorKey === FLOOR_B_KEY) return LABEL_B; | |||||
| return undefined; | return undefined; | ||||
| }, [fgPickOrders]); | }, [fgPickOrders]); | ||||
| @@ -1037,7 +1038,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| .replace(/\//g, '') | .replace(/\//g, '') | ||||
| .replace(/\s/g, ''); | .replace(/\s/g, ''); | ||||
| const pickOrderLinesForDisplay = | const pickOrderLinesForDisplay = | ||||
| doFloorKey === '2F' | |||||
| doFloorKey === FLOOR_A_KEY | |||||
| ? [...(mergedPickOrder.pickOrderLines || [])].sort((a: any, b: any) => { | ? [...(mergedPickOrder.pickOrderLines || [])].sort((a: any, b: any) => { | ||||
| const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999; | const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999; | ||||
| const bo = b.itemOrder != null ? Number(b.itemOrder) : 999999; | const bo = b.itemOrder != null ? Number(b.itemOrder) : 999999; | ||||
| @@ -5,6 +5,7 @@ | |||||
| * 「掃碼提貨」 per listed lot row (parent calls `workbenchScanPick` with `inventoryLotLineId`). | * 「掃碼提貨」 per listed lot row (parent calls `workbenchScanPick` with `inventoryLotLineId`). | ||||
| */ | */ | ||||
| import { LOC_PREFIX } from "@/config/demoFloors"; | |||||
| import React, { | import React, { | ||||
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| @@ -455,7 +456,8 @@ const WorkbenchLotLabelPrintModal: React.FC<WorkbenchLotLabelPrintModalProps> = | |||||
| return availableLots.filter((lot) => { | return availableLots.filter((lot) => { | ||||
| // 使用者從本列開啟視窗:即使 API 未帶 warehouseCode,仍應顯示目前這筆批號 | // 使用者從本列開啟視窗:即使 API 未帶 warehouseCode,仍應顯示目前這筆批號 | ||||
| if (lot._scanned) return true; | if (lot._scanned) return true; | ||||
| return String(lot.warehouseCode ?? "").startsWith(prefix); | |||||
| const code = String(lot.warehouseCode ?? ""); | |||||
| return code.startsWith(prefix) || code.startsWith(LOC_PREFIX); | |||||
| }); | }); | ||||
| }, [availableLots, warehouseCodePrefixFilter]); | }, [availableLots, warehouseCodePrefixFilter]); | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import React, { useCallback, useEffect, useMemo, useState } from "react"; | import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -278,8 +279,8 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => { | |||||
| displayEmpty | displayEmpty | ||||
| > | > | ||||
| <MenuItem value="">{t("All Floors")}</MenuItem> | <MenuItem value="">{t("All Floors")}</MenuItem> | ||||
| <MenuItem value="2/F">2/F</MenuItem> | |||||
| <MenuItem value="4/F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{FLOOR_B}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <FormControl sx={{ minWidth: 150 }} size="small"> | <FormControl sx={{ minWidth: 150 }} size="small"> | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| export type WorkbenchLaneDateKey = "today" | "tomorrow" | "dayAfterTomorrow"; | export type WorkbenchLaneDateKey = "today" | "tomorrow" | "dayAfterTomorrow"; | ||||
| export type WorkbenchLaneFloor = "2/F" | "4/F"; | |||||
| export type WorkbenchLaneFloor = typeof FLOOR_A | typeof FLOOR_B; | |||||
| /** Tab 0 lane assignment filters — lifted to DoWorkbenchTabs so they survive tab switches. */ | /** Tab 0 lane assignment filters — lifted to DoWorkbenchTabs so they survive tab switches. */ | ||||
| export type WorkbenchLanePanelPrefs = { | export type WorkbenchLanePanelPrefs = { | ||||
| @@ -10,7 +11,7 @@ export type WorkbenchLanePanelPrefs = { | |||||
| export const DEFAULT_WORKBENCH_LANE_PANEL_PREFS: WorkbenchLanePanelPrefs = { | export const DEFAULT_WORKBENCH_LANE_PANEL_PREFS: WorkbenchLanePanelPrefs = { | ||||
| selectedDate: "today", | selectedDate: "today", | ||||
| ticketFloor: "2/F", | |||||
| ticketFloor: FLOOR_A, | |||||
| releaseType: "batch", | releaseType: "batch", | ||||
| }; | }; | ||||
| @@ -19,5 +20,5 @@ export function isWorkbenchLaneDateKey(v: string): v is WorkbenchLaneDateKey { | |||||
| } | } | ||||
| export function isWorkbenchLaneFloor(v: string): v is WorkbenchLaneFloor { | export function isWorkbenchLaneFloor(v: string): v is WorkbenchLaneFloor { | ||||
| return v === "2/F" || v === "4/F"; | |||||
| return v === FLOOR_A || v === FLOOR_B; | |||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel, Card, CardContent } from "@mui/material"; | import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel, Card, CardContent } from "@mui/material"; | ||||
| import { useCallback, useEffect, useState } from "react"; | import { useCallback, useEffect, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -28,8 +29,8 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| try { | try { | ||||
| const [s2, s4] = await Promise.all([ | const [s2, s4] = await Promise.all([ | ||||
| fetchStoreLaneSummary("2/F"), | |||||
| fetchStoreLaneSummary("4/F") | |||||
| fetchStoreLaneSummary(FLOOR_A), | |||||
| fetchStoreLaneSummary(FLOOR_B) | |||||
| ]); | ]); | ||||
| setSummary2F(s2); | setSummary2F(s2); | ||||
| setSummary4F(s4); | setSummary4F(s4); | ||||
| @@ -169,7 +170,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| color: '#1976d2' | color: '#1976d2' | ||||
| }} | }} | ||||
| > | > | ||||
| 2/F | |||||
| {FLOOR_A} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| @@ -232,7 +233,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| variant="outlined" | variant="outlined" | ||||
| size="medium" | size="medium" | ||||
| disabled={item.lane.unassigned === 0 || isAssigning} | disabled={item.lane.unassigned === 0 || isAssigning} | ||||
| onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} | |||||
| onClick={() => handleAssignByLane(FLOOR_A, item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} | |||||
| sx={{ | sx={{ | ||||
| flex: 1, | flex: 1, | ||||
| fontSize: '1.1rem', | fontSize: '1.1rem', | ||||
| @@ -282,7 +283,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| color: '#1976d2' | color: '#1976d2' | ||||
| }} | }} | ||||
| > | > | ||||
| 4/F | |||||
| {FLOOR_B} | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| @@ -345,7 +346,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned }) => | |||||
| variant="outlined" | variant="outlined" | ||||
| size="medium" | size="medium" | ||||
| disabled={item.lane.unassigned === 0 || isAssigning} | disabled={item.lane.unassigned === 0 || isAssigning} | ||||
| onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} | |||||
| onClick={() => handleAssignByLane(FLOOR_B, item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} | |||||
| sx={{ | sx={{ | ||||
| flex: 1, | flex: 1, | ||||
| fontSize: '1.1rem', | fontSize: '1.1rem', | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| /** | /** | ||||
| * 權限說明(與全站一致): | * 權限說明(與全站一致): | ||||
| * - 登入後 JWT / session 帶有 `abilities: string[]`(見 config/authConfig、authorities.ts)。 | * - 登入後 JWT / session 帶有 `abilities: string[]`(見 config/authConfig、authorities.ts)。 | ||||
| @@ -303,8 +304,8 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| displayEmpty | displayEmpty | ||||
| > | > | ||||
| <MenuItem value="">{t("All Floors")}</MenuItem> | <MenuItem value="">{t("All Floors")}</MenuItem> | ||||
| <MenuItem value="2/F">2/F</MenuItem> | |||||
| <MenuItem value="4/F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{FLOOR_B}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
| import { | import { | ||||
| Alert, | Alert, | ||||
| @@ -29,7 +30,7 @@ import { | |||||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| type FloorFilter = "all" | "2/F" | "4/F"; | |||||
| type FloorFilter = "all" | typeof FLOOR_A | typeof FLOOR_B; | |||||
| type BreakdownDimension = "lane" | "shop" | "floor"; | type BreakdownDimension = "lane" | "shop" | "floor"; | ||||
| type DailySummaryRow = { | type DailySummaryRow = { | ||||
| @@ -361,10 +362,10 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| total: 0, | total: 0, | ||||
| }; | }; | ||||
| if (record.storeId === "2/F") { | |||||
| if (record.storeId === FLOOR_A) { | |||||
| current.floor2F += cartonQty; | current.floor2F += cartonQty; | ||||
| } | } | ||||
| if (record.storeId === "4/F") { | |||||
| if (record.storeId === FLOOR_B) { | |||||
| current.floor4F += cartonQty; | current.floor4F += cartonQty; | ||||
| } | } | ||||
| if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) { | if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) { | ||||
| @@ -390,18 +391,18 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| let truckX = 0; | let truckX = 0; | ||||
| filteredRecords.forEach((record) => { | filteredRecords.forEach((record) => { | ||||
| const cartonQty = Number(record.numberOfCartons ?? 0); | const cartonQty = Number(record.numberOfCartons ?? 0); | ||||
| if (record.storeId === "2/F") floor2F += cartonQty; | |||||
| if (record.storeId === "4/F") floor4F += cartonQty; | |||||
| if (record.storeId === FLOOR_A) floor2F += cartonQty; | |||||
| if (record.storeId === FLOOR_B) floor4F += cartonQty; | |||||
| if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) truckX += cartonQty; | if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) truckX += cartonQty; | ||||
| }); | }); | ||||
| return toSorted( | return toSorted( | ||||
| [ | [ | ||||
| { key: "2/F", label: "2/F", qty: floor2F }, | |||||
| { key: "4/F", label: "4/F", qty: floor4F }, | |||||
| { key: FLOOR_A, label: FLOOR_A, qty: floor2F }, | |||||
| { key: FLOOR_B, label: FLOOR_B, qty: floor4F }, | |||||
| { key: TRUCK_X_LANE, label: shortLaneLabel(TRUCK_X_LANE), qty: truckX }, | { key: TRUCK_X_LANE, label: shortLaneLabel(TRUCK_X_LANE), qty: truckX }, | ||||
| ].filter((row) => { | ].filter((row) => { | ||||
| if (row.qty > 0) return true; | if (row.qty > 0) return true; | ||||
| if (row.key === "2/F" || row.key === "4/F") return floor === row.key; | |||||
| if (row.key === FLOOR_A || row.key === FLOOR_B) return floor === row.key; | |||||
| return lane === TRUCK_X_LANE && row.key === TRUCK_X_LANE; | return lane === TRUCK_X_LANE && row.key === TRUCK_X_LANE; | ||||
| }), | }), | ||||
| ); | ); | ||||
| @@ -490,7 +491,7 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| (row: BreakdownRow) => { | (row: BreakdownRow) => { | ||||
| if (breakdownDimension === "lane") return lane === row.key; | if (breakdownDimension === "lane") return lane === row.key; | ||||
| if (breakdownDimension === "shop") return shop === row.key; | if (breakdownDimension === "shop") return shop === row.key; | ||||
| if (row.key === "2/F" || row.key === "4/F") return floor === row.key; | |||||
| if (row.key === FLOOR_A || row.key === FLOOR_B) return floor === row.key; | |||||
| return lane === TRUCK_X_LANE && row.key === TRUCK_X_LANE; | return lane === TRUCK_X_LANE && row.key === TRUCK_X_LANE; | ||||
| }, | }, | ||||
| [breakdownDimension, floor, lane, shop], | [breakdownDimension, floor, lane, shop], | ||||
| @@ -550,8 +551,8 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| }; | }; | ||||
| if (!current.shopName && shopName) current.shopName = shopName; | if (!current.shopName && shopName) current.shopName = shopName; | ||||
| if (record.storeId === "2/F") current.floor2F += cartonQty; | |||||
| if (record.storeId === "4/F") current.floor4F += cartonQty; | |||||
| if (record.storeId === FLOOR_A) current.floor2F += cartonQty; | |||||
| if (record.storeId === FLOOR_B) current.floor4F += cartonQty; | |||||
| if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) current.truckX += cartonQty; | if (normalizeTruckLane(record.truckLanceCode) === TRUCK_X_LANE) current.truckX += cartonQty; | ||||
| current.total += cartonQty; | current.total += cartonQty; | ||||
| @@ -677,8 +678,8 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| t("Date"), | t("Date"), | ||||
| t("Shop code"), | t("Shop code"), | ||||
| t("Shop Name"), | t("Shop Name"), | ||||
| t("2/F carton qty"), | |||||
| t("4/F carton qty"), | |||||
| t(FLOOR_A + " carton qty"), | |||||
| t(FLOOR_B + " carton qty"), | |||||
| t("Truck X carton qty"), | t("Truck X carton qty"), | ||||
| t("Total carton qty"), | t("Total carton qty"), | ||||
| ], | ], | ||||
| @@ -693,8 +694,8 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| ]), | ]), | ||||
| [...blank], | [...blank], | ||||
| [t("Summary"), ...blank.slice(1)], | [t("Summary"), ...blank.slice(1)], | ||||
| [t("2/F carton qty"), "", "", reportSummary.floor2F, "", "", ""], | |||||
| [t("4/F carton qty"), "", "", "", reportSummary.floor4F, "", ""], | |||||
| [t(FLOOR_A + " carton qty"), "", "", reportSummary.floor2F, "", "", ""], | |||||
| [t(FLOOR_B + " carton qty"), "", "", "", reportSummary.floor4F, "", ""], | |||||
| [t("Truck X carton qty"), "", "", "", "", reportSummary.truckX, ""], | [t("Truck X carton qty"), "", "", "", "", reportSummary.truckX, ""], | ||||
| [t("Total carton qty"), "", "", "", "", "", reportSummary.total], | [t("Total carton qty"), "", "", "", "", "", reportSummary.total], | ||||
| ]; | ]; | ||||
| @@ -990,8 +991,8 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| onChange={(event) => setFloor(event.target.value as FloorFilter)} | onChange={(event) => setFloor(event.target.value as FloorFilter)} | ||||
| > | > | ||||
| <MenuItem value={ALL}>{t("All")}</MenuItem> | <MenuItem value={ALL}>{t("All")}</MenuItem> | ||||
| <MenuItem value="2/F">2/F</MenuItem> | |||||
| <MenuItem value="4/F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{FLOOR_B}</MenuItem> | |||||
| </TextField> | </TextField> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} sm={6} md={3}> | <Grid item xs={12} sm={6} md={3}> | ||||
| @@ -1041,8 +1042,8 @@ const FinishedGoodCartonDashboardTab: React.FC<Props> = ({ mode = "normal" }) => | |||||
| <Paper sx={{ px: 1, py: 1 }}> | <Paper sx={{ px: 1, py: 1 }}> | ||||
| <Grid container> | <Grid container> | ||||
| {[ | {[ | ||||
| { label: t("2/F carton qty"), value: summary.floor2F }, | |||||
| { label: t("4/F carton qty"), value: summary.floor4F }, | |||||
| { label: t(FLOOR_A + " carton qty"), value: summary.floor2F }, | |||||
| { label: t(FLOOR_B + " carton qty"), value: summary.floor4F }, | |||||
| { label: t("Truck X carton qty"), value: summary.truckX }, | { label: t("Truck X carton qty"), value: summary.truckX }, | ||||
| { label: t("Total carton qty"), value: summary.total }, | { label: t("Total carton qty"), value: summary.total }, | ||||
| ].map((kpi, index) => ( | ].map((kpi, index) => ( | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel ,Tooltip} from "@mui/material"; | import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel ,Tooltip} from "@mui/material"; | ||||
| import { useCallback, useEffect, useMemo, useState, useRef } from "react"; | import { useCallback, useEffect, useMemo, useState, useRef } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -22,7 +23,7 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const [selectedStore, setSelectedStore] = useState<string>("2/F"); | |||||
| const [selectedStore, setSelectedStore] = useState<string>(FLOOR_A); | |||||
| const [selectedTruck, setSelectedTruck] = useState<string>(""); | const [selectedTruck, setSelectedTruck] = useState<string>(""); | ||||
| const [selectedDefaultTruck, setSelectedDefaultTruck] = useState<string>(""); | const [selectedDefaultTruck, setSelectedDefaultTruck] = useState<string>(""); | ||||
| const [modalOpen, setModalOpen] = useState(false); | const [modalOpen, setModalOpen] = useState(false); | ||||
| @@ -44,7 +45,7 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to | |||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | const [selectedDate, setSelectedDate] = useState<string>("today"); | ||||
| const [releaseType, setReleaseType] = useState<string>("batch"); | const [releaseType, setReleaseType] = useState<string>("batch"); | ||||
| const [ticketFloor, setTicketFloor] = useState<"2/F" | "4/F">("2/F"); | |||||
| const [ticketFloor, setTicketFloor] = useState<typeof FLOOR_A | typeof FLOOR_B>(FLOOR_A); | |||||
| const startFullTimer = () => { | const startFullTimer = () => { | ||||
| if (typeof window === "undefined") return; | if (typeof window === "undefined") return; | ||||
| const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; | const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; | ||||
| @@ -79,8 +80,8 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to | |||||
| } | } | ||||
| const [s2, s4] = await Promise.all([ | const [s2, s4] = await Promise.all([ | ||||
| fetchStoreLaneSummary("2/F", dateParam, releaseType), | |||||
| fetchStoreLaneSummary("4/F", dateParam, releaseType) | |||||
| fetchStoreLaneSummary(FLOOR_A, dateParam, releaseType), | |||||
| fetchStoreLaneSummary(FLOOR_B, dateParam, releaseType) | |||||
| ]); | ]); | ||||
| setSummary2F(s2); | setSummary2F(s2); | ||||
| setSummary4F(s4); | setSummary4F(s4); | ||||
| @@ -117,8 +118,8 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to | |||||
| startFullTimer(); | startFullTimer(); | ||||
| try { | try { | ||||
| const [list2F, list4F] = await Promise.all([ | const [list2F, list4F] = await Promise.all([ | ||||
| fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, "4/F"), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, FLOOR_A), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, FLOOR_B), | |||||
| ]); | ]); | ||||
| const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | ||||
| const map: Record<string, number> = {}; | const map: Record<string, number> = {}; | ||||
| @@ -388,10 +389,10 @@ const getDateLabel = (offset: number) => { | |||||
| id="ticket-floor-select" | id="ticket-floor-select" | ||||
| value={ticketFloor} | value={ticketFloor} | ||||
| label={t("Floor ticket")} | label={t("Floor ticket")} | ||||
| onChange={(e) => setTicketFloor(e.target.value as "2/F" | "4/F")} | |||||
| onChange={(e) => setTicketFloor(e.target.value as typeof FLOOR_A | typeof FLOOR_B)} | |||||
| > | > | ||||
| <MenuItem value="2/F">{t("2F ticket")}</MenuItem> | |||||
| <MenuItem value="4/F">{t("4F ticket")}</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{t(FLOOR_A_KEY + " ticket")}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{t(FLOOR_B_KEY + " ticket")}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| </Box> | </Box> | ||||
| @@ -414,7 +415,7 @@ const getDateLabel = (offset: number) => { | |||||
| {/* Grid containing both floors */} | {/* Grid containing both floors */} | ||||
| <Grid container spacing={2}> | <Grid container spacing={2}> | ||||
| {/* 2/F 楼层面板 */} | {/* 2/F 楼层面板 */} | ||||
| {ticketFloor === "2/F" && ( | |||||
| {ticketFloor === FLOOR_A && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| {/* Floor Label */} | {/* Floor Label */} | ||||
| @@ -426,7 +427,7 @@ const getDateLabel = (offset: number) => { | |||||
| pt: 1 | pt: 1 | ||||
| }} | }} | ||||
| > | > | ||||
| 2/F | |||||
| {FLOOR_A} | |||||
| </Typography> | </Typography> | ||||
| {/* Content Box */} | {/* Content Box */} | ||||
| @@ -495,7 +496,7 @@ const getDateLabel = (offset: number) => { | |||||
| disabled={lane.unassigned === 0 || isAssigning} | disabled={lane.unassigned === 0 || isAssigning} | ||||
| onClick={() => | onClick={() => | ||||
| handleLaneButtonClick( | handleLaneButtonClick( | ||||
| "2/F", | |||||
| FLOOR_A, | |||||
| row.truckDepartureTime, | row.truckDepartureTime, | ||||
| lane.truckLanceCode, | lane.truckLanceCode, | ||||
| null, | null, | ||||
| @@ -530,7 +531,7 @@ const getDateLabel = (offset: number) => { | |||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| {ticketFloor === "4/F" && ( | |||||
| {ticketFloor === FLOOR_B && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| {/* Floor Label */} | {/* Floor Label */} | ||||
| @@ -542,7 +543,7 @@ const getDateLabel = (offset: number) => { | |||||
| pt: 1 | pt: 1 | ||||
| }} | }} | ||||
| > | > | ||||
| 4/F | |||||
| {FLOOR_B} | |||||
| </Typography> | </Typography> | ||||
| {/* Content Box */} | {/* Content Box */} | ||||
| @@ -613,7 +614,7 @@ const getDateLabel = (offset: number) => { | |||||
| disabled={slot.lane.unassigned === 0 || isAssigning} | disabled={slot.lane.unassigned === 0 || isAssigning} | ||||
| onClick={() => | onClick={() => | ||||
| handleLaneButtonClick( | handleLaneButtonClick( | ||||
| "4/F", | |||||
| FLOOR_B, | |||||
| slot.truckDepartureTime, | slot.truckDepartureTime, | ||||
| slot.lane.truckLanceCode, | slot.lane.truckLanceCode, | ||||
| slot.lane.loadingSequence ?? null, | slot.lane.loadingSequence ?? null, | ||||
| @@ -720,11 +721,11 @@ const getDateLabel = (offset: number) => { | |||||
| </Box> | </Box> | ||||
| </Grid> | </Grid> | ||||
| {ticketFloor === "2/F" && ( | |||||
| {ticketFloor === FLOOR_A && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | ||||
| 2/F | |||||
| {FLOOR_A} | |||||
| </Typography> | </Typography> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| @@ -751,7 +752,7 @@ const getDateLabel = (offset: number) => { | |||||
| ) : ( | ) : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| {truckCounts2F.map(({ truck, count }) => ( | {truckCounts2F.map(({ truck, count }) => ( | ||||
| <Grid item xs={6} sm={4} md={3} key={`2F-${truck}`} sx={{ display: "flex" }}> | |||||
| <Grid item xs={6} sm={4} md={3} key={`${FLOOR_A_KEY}-${truck}`} sx={{ display: "flex" }}> | |||||
| <Button | <Button | ||||
| @@ -759,7 +760,7 @@ const getDateLabel = (offset: number) => { | |||||
| size="medium" | size="medium" | ||||
| onClick={() => { | onClick={() => { | ||||
| setIsDefaultTruck(false); | setIsDefaultTruck(false); | ||||
| setSelectedStore("2/F"); | |||||
| setSelectedStore(FLOOR_A); | |||||
| setSelectedTruck(truck); | setSelectedTruck(truck); | ||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| @@ -788,11 +789,11 @@ const getDateLabel = (offset: number) => { | |||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| )} | )} | ||||
| {ticketFloor === "4/F" && ( | |||||
| {ticketFloor === FLOOR_B && ( | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | ||||
| 4/F | |||||
| {FLOOR_B} | |||||
| </Typography> | </Typography> | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| @@ -819,7 +820,7 @@ const getDateLabel = (offset: number) => { | |||||
| ) : ( | ) : ( | ||||
| <Grid container spacing={1}> | <Grid container spacing={1}> | ||||
| {truckCounts4F.map(({ truck, count }) => ( | {truckCounts4F.map(({ truck, count }) => ( | ||||
| <Grid item xs={6} sm={4} md={3} key={`4F-${truck}`} sx={{ display: "flex" }}> | |||||
| <Grid item xs={6} sm={4} md={3} key={`${FLOOR_B_KEY}-${truck}`} sx={{ display: "flex" }}> | |||||
| <Button | <Button | ||||
| @@ -827,7 +828,7 @@ const getDateLabel = (offset: number) => { | |||||
| size="medium" | size="medium" | ||||
| onClick={() => { | onClick={() => { | ||||
| setIsDefaultTruck(false); | setIsDefaultTruck(false); | ||||
| setSelectedStore("4/F"); | |||||
| setSelectedStore(FLOOR_B); | |||||
| setSelectedTruck(truck); | setSelectedTruck(truck); | ||||
| setModalOpen(true); | setModalOpen(true); | ||||
| }} | }} | ||||
| @@ -877,7 +878,7 @@ const getDateLabel = (offset: number) => { | |||||
| variant="outlined" | variant="outlined" | ||||
| size="medium" | size="medium" | ||||
| onClick={() => { | onClick={() => { | ||||
| setSelectedStore("4/F"); // 或用專門標示 Truck X | |||||
| setSelectedStore(FLOOR_B); // 或用專門標示 Truck X | |||||
| setSelectedTruck("車線-X"); | setSelectedTruck("車線-X"); | ||||
| setIsDefaultTruck(true); | setIsDefaultTruck(true); | ||||
| setDefaultDateScope("before"); // 類似上一輪說的 dateScope | setDefaultDateScope("before"); // 類似上一輪說的 dateScope | ||||
| @@ -902,8 +903,8 @@ const getDateLabel = (offset: number) => { | |||||
| const loadCounts = async () => { | const loadCounts = async () => { | ||||
| try { | try { | ||||
| const [list2F, list4F] = await Promise.all([ | const [list2F, list4F] = await Promise.all([ | ||||
| fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, "4/F"), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, FLOOR_A), | |||||
| fetchReleasedDoPickOrdersForSelection(undefined, FLOOR_B), | |||||
| ]); | ]); | ||||
| const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | ||||
| const map: Record<string, number> = {}; | const map: Record<string, number> = {}; | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { PickOrderResult } from "@/app/api/pickOrder"; | import { PickOrderResult } from "@/app/api/pickOrder"; | ||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| @@ -134,8 +135,8 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCo | |||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| try { | try { | ||||
| const [s2, s4] = await Promise.all([ | const [s2, s4] = await Promise.all([ | ||||
| fetchStoreLaneSummary("2/F"), | |||||
| fetchStoreLaneSummary("4/F") | |||||
| fetchStoreLaneSummary(FLOOR_A), | |||||
| fetchStoreLaneSummary(FLOOR_B) | |||||
| ]); | ]); | ||||
| setSummary2F(s2); | setSummary2F(s2); | ||||
| setSummary4F(s4); | setSummary4F(s4); | ||||
| @@ -300,7 +301,7 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCo | |||||
| Swal.fire({ | Swal.fire({ | ||||
| position: "bottom-end", | position: "bottom-end", | ||||
| icon: "warning", | icon: "warning", | ||||
| text: "請先選擇 2/F 或 4/F、車線和日期", | |||||
| text: "請先選擇 " + FLOOR_A + " 或 " + FLOOR_B + "、車線和日期", | |||||
| showConfirmButton: false, | showConfirmButton: false, | ||||
| timer: 1800, | timer: 1800, | ||||
| }); | }); | ||||
| @@ -1,6 +1,7 @@ | |||||
| /* | /* | ||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY, LABEL_A, LABEL_B } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -786,7 +787,7 @@ const PickExecution: React.FC<Props> = ({ | |||||
| .trim() | .trim() | ||||
| .toUpperCase() | .toUpperCase() | ||||
| .replace(/\s/g, ""); | .replace(/\s/g, ""); | ||||
| // e.g. "2/F" -> "2F-", "4/F" -> "4F-" | |||||
| // e.g. FLOOR_A -> FLOOR_A_KEY + "-", FLOOR_B -> FLOOR_B_KEY + "-" | |||||
| const floorKey = storeId.replace(/\//g, ""); | const floorKey = storeId.replace(/\//g, ""); | ||||
| return floorKey ? `${floorKey}-` : ""; | return floorKey ? `${floorKey}-` : ""; | ||||
| }, [fgPickOrders]); | }, [fgPickOrders]); | ||||
| @@ -796,8 +797,8 @@ const PickExecution: React.FC<Props> = ({ | |||||
| .toUpperCase() | .toUpperCase() | ||||
| .replace(/\s/g, ""); | .replace(/\s/g, ""); | ||||
| const floorKey = storeId.replace(/\//g, ""); | const floorKey = storeId.replace(/\//g, ""); | ||||
| if (floorKey === "2F") return "Label機 2F A+B"; | |||||
| if (floorKey === "4F") return "Label機 4F 乾貨 C, D"; | |||||
| if (floorKey === FLOOR_A_KEY) return LABEL_A; | |||||
| if (floorKey === FLOOR_B_KEY) return LABEL_B; | |||||
| return undefined; | return undefined; | ||||
| }, [fgPickOrders]); | }, [fgPickOrders]); | ||||
| // Add these missing state variables after line 352 | // Add these missing state variables after line 352 | ||||
| @@ -1212,14 +1213,14 @@ const PickExecution: React.FC<Props> = ({ | |||||
| // 将层级数据转换为平铺格式(用于表格显示) | // 将层级数据转换为平铺格式(用于表格显示) | ||||
| const flatLotData: any[] = []; | const flatLotData: any[] = []; | ||||
| // 2/F 與後端 store_id 一致時需按 itemOrder;避免 API 未走 2F 分支時畫面仍亂序 | |||||
| // ${FLOOR_A} 與後端 store_id 一致時需按 itemOrder;避免 API 未走 ${FLOOR_A_KEY} 分支時畫面仍亂序 | |||||
| const doFloorKey = String(hierarchicalData.fgInfo.storeId ?? "") | const doFloorKey = String(hierarchicalData.fgInfo.storeId ?? "") | ||||
| .trim() | .trim() | ||||
| .toUpperCase() | .toUpperCase() | ||||
| .replace(/\//g, "") | .replace(/\//g, "") | ||||
| .replace(/\s/g, ""); | .replace(/\s/g, ""); | ||||
| const pickOrderLinesForDisplay = | const pickOrderLinesForDisplay = | ||||
| doFloorKey === "2F" | |||||
| doFloorKey === FLOOR_A_KEY | |||||
| ? [...(mergedPickOrder.pickOrderLines || [])].sort( | ? [...(mergedPickOrder.pickOrderLines || [])].sort( | ||||
| (a: any, b: any) => { | (a: any, b: any) => { | ||||
| const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999; | const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999; | ||||
| @@ -1,6 +1,7 @@ | |||||
| // 新建檔案 | // 新建檔案 | ||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Modal, | Modal, | ||||
| Box, | Box, | ||||
| @@ -37,7 +38,7 @@ export type ReleasedDoPickListBridge = { | |||||
| storeId?: string, | storeId?: string, | ||||
| truck?: string, | truck?: string, | ||||
| releaseType?: string, | releaseType?: string, | ||||
| /** Optional `2F`/`4F` for Truck X supplier-floor split */ | |||||
| /** Optional `${FLOOR_A_KEY}`/`${FLOOR_B_KEY}` for Truck X supplier-floor split */ | |||||
| floor?: string | floor?: string | ||||
| ) => Promise<ReleasedDoPickOrderListItem[]>; | ) => Promise<ReleasedDoPickOrderListItem[]>; | ||||
| /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */ | /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */ | ||||
| @@ -47,7 +48,7 @@ export type ReleasedDoPickListBridge = { | |||||
| truck?: string, | truck?: string, | ||||
| requiredDeliveryDate?: string, | requiredDeliveryDate?: string, | ||||
| releaseType?: string, | releaseType?: string, | ||||
| /** Optional `2F`/`4F` for Truck X supplier-floor split */ | |||||
| /** Optional `${FLOOR_A_KEY}`/`${FLOOR_B_KEY}` for Truck X supplier-floor split */ | |||||
| floor?: string | floor?: string | ||||
| ) => Promise<ReleasedDoPickOrderListItem[]>; | ) => Promise<ReleasedDoPickOrderListItem[]>; | ||||
| assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>; | assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>; | ||||
| @@ -75,7 +76,7 @@ interface Props { | |||||
| */ | */ | ||||
| filterRequiredDeliveryDate?: string; | filterRequiredDeliveryDate?: string; | ||||
| /** | /** | ||||
| * Truck X only: `2F`/`4F` (or `2/F`/`4/F`) — filter by DO supplier preferred floor; storeId stays null. | |||||
| * Truck X only: `${FLOOR_A_KEY}`/`${FLOOR_B_KEY}` (or `${FLOOR_A}`/`${FLOOR_B}`) — filter by DO supplier preferred floor; storeId stays null. | |||||
| */ | */ | ||||
| truckXFloor?: string; | truckXFloor?: string; | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||
| import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material"; | import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material"; | ||||
| import DownloadIcon from "@mui/icons-material/Download"; | import DownloadIcon from "@mui/icons-material/Download"; | ||||
| @@ -121,7 +122,7 @@ const TruckRoutingSummaryTab: React.FC<Props> = ({ onFiltersChange }) => { | |||||
| <TextField | <TextField | ||||
| select | select | ||||
| fullWidth | fullWidth | ||||
| label="2/F 或 4/F" | |||||
| label={FLOOR_A + " 或 " + FLOOR_B} | |||||
| value={storeId} | value={storeId} | ||||
| onChange={(e) => onStoreChange(e.target.value)} | onChange={(e) => onStoreChange(e.target.value)} | ||||
| > | > | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, LOC_PREFIX } from "@/config/demoFloors"; | |||||
| import React, { | import React, { | ||||
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| @@ -77,7 +78,7 @@ export interface LotLabelPrintModalProps { | |||||
| reminderText?: string; | reminderText?: string; | ||||
| /** 額外標題(顯示在最上方,reminderText 之下) */ | /** 額外標題(顯示在最上方,reminderText 之下) */ | ||||
| statusTitleText?: string; | statusTitleText?: string; | ||||
| /** 只顯示特定倉位前綴(例如 "2F-") */ | |||||
| /** 只顯示特定倉位前綴(例如 FLOOR_A_KEY + "-") */ | |||||
| warehouseCodePrefixFilter?: string; | warehouseCodePrefixFilter?: string; | ||||
| /** 不顯示觸發視窗的批號(analysis.scanned) */ | /** 不顯示觸發視窗的批號(analysis.scanned) */ | ||||
| hideTriggeredLot?: boolean; | hideTriggeredLot?: boolean; | ||||
| @@ -346,9 +347,10 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| const filteredLots = useMemo(() => { | const filteredLots = useMemo(() => { | ||||
| const prefix = String(warehouseCodePrefixFilter ?? "").trim(); | const prefix = String(warehouseCodePrefixFilter ?? "").trim(); | ||||
| if (!prefix) return availableLots; | if (!prefix) return availableLots; | ||||
| return availableLots.filter((lot) => | |||||
| String(lot.warehouseCode ?? "").startsWith(prefix), | |||||
| ); | |||||
| return availableLots.filter((lot) => { | |||||
| const code = String(lot.warehouseCode ?? ""); | |||||
| return code.startsWith(prefix) || code.startsWith(LOC_PREFIX); | |||||
| }); | |||||
| }, [availableLots, warehouseCodePrefixFilter]); | }, [availableLots, warehouseCodePrefixFilter]); | ||||
| const selectedPrinter = useMemo(() => { | const selectedPrinter = useMemo(() => { | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import React, { useCallback, useEffect, useState } from "react"; | import React, { useCallback, useEffect, useState } from "react"; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -45,7 +46,7 @@ const JoPickOrderList: React.FC<Props> = ({ printerCombo }) => { | |||||
| const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined); | const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined); | ||||
| const [searchQuery, setSearchQuery] = useState<Record<string, any>>({ planStart: today }); | const [searchQuery, setSearchQuery] = useState<Record<string, any>>({ planStart: today }); | ||||
| type FloorFilter = "ALL" | "2F" | "3F" | "4F" | "NO_LOT"; | |||||
| type FloorFilter = "ALL" | typeof FLOOR_A_KEY | typeof FLOOR_D_KEY | typeof FLOOR_B_KEY | "NO_LOT"; | |||||
| const searchCriteria: Criterion<any>[] = [ | const searchCriteria: Criterion<any>[] = [ | ||||
| { label: t("Job Order Code"), paramName: "jobOrderCode", type: "text" }, | { label: t("Job Order Code"), paramName: "jobOrderCode", type: "text" }, | ||||
| @@ -76,9 +77,9 @@ const JoPickOrderList: React.FC<Props> = ({ printerCombo }) => { | |||||
| paramName: "floor", | paramName: "floor", | ||||
| type: "select-labelled", | type: "select-labelled", | ||||
| options: [ | options: [ | ||||
| { label: "2F", value: "2F" }, | |||||
| { label: "3F", value: "3F" }, | |||||
| { label: "4F", value: "4F" }, | |||||
| { label: FLOOR_A_KEY, value: FLOOR_A_KEY }, | |||||
| { label: FLOOR_D_KEY, value: FLOOR_D_KEY }, | |||||
| { label: FLOOR_B_KEY, value: FLOOR_B_KEY }, | |||||
| { label: t("No Lot"), value: "NO_LOT" }, | { label: t("No Lot"), value: "NO_LOT" }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -86,7 +87,7 @@ const JoPickOrderList: React.FC<Props> = ({ printerCombo }) => { | |||||
| const selectedFloor: FloorFilter = (() => { | const selectedFloor: FloorFilter = (() => { | ||||
| const floor = String(searchQuery.floor || "ALL"); | const floor = String(searchQuery.floor || "ALL"); | ||||
| if (floor === "2F" || floor === "3F" || floor === "4F" || floor === "NO_LOT") { | |||||
| if (floor === FLOOR_A_KEY || floor === FLOOR_D_KEY || floor === FLOOR_B_KEY || floor === "NO_LOT") { | |||||
| return floor; | return floor; | ||||
| } | } | ||||
| return "ALL"; | return "ALL"; | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_C_KEY, FLOOR_D_KEY, LABEL_A, LABEL_B } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -1017,17 +1018,22 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList, printerCo | |||||
| [], | [], | ||||
| ); | ); | ||||
| const extractFloor = (lot: any): string => { | const extractFloor = (lot: any): string => { | ||||
| const raw = lot.routerRoute || lot.routerArea || lot.location || ""; | |||||
| const match = raw.match(/^(\d+F?)/i) || raw.split("-")[0]; | |||||
| return (match?.[1] || match || raw || "") | |||||
| .toUpperCase() | |||||
| .replace(/(\d)F?/i, "$1F"); | |||||
| const raw = String(lot.routerRoute || lot.routerArea || lot.location || ""); | |||||
| const head = raw.trim().toUpperCase().replace(/\//g, "").split("-")[0]; | |||||
| if (head === "2" || head === FLOOR_A_KEY) return FLOOR_A_KEY; | |||||
| if (head === "4" || head === FLOOR_B_KEY) return FLOOR_B_KEY; | |||||
| if (head === "3" || head === FLOOR_D_KEY) return FLOOR_D_KEY; | |||||
| if (head === "1" || head === FLOOR_C_KEY) return FLOOR_C_KEY; | |||||
| return head; | |||||
| }; | }; | ||||
| // 楼层排序权重:4F > 3F > 2F(数字越大越靠前) | |||||
| // B/F, then D/F, then A/F. | |||||
| const floorSortOrder = (floor: string): number => { | const floorSortOrder = (floor: string): number => { | ||||
| const n = parseInt(floor.replace(/\D/g, ""), 10); | |||||
| return isNaN(n) ? 0 : n; | |||||
| if (floor === FLOOR_B_KEY) return 4; | |||||
| if (floor === FLOOR_D_KEY) return 3; | |||||
| if (floor === FLOOR_A_KEY) return 2; | |||||
| if (floor === FLOOR_C_KEY) return 1; | |||||
| return 0; | |||||
| }; | }; | ||||
| const combinedLotData = useMemo(() => { | const combinedLotData = useMemo(() => { | ||||
| const lots = getAllLotsFromHierarchical(jobOrderData); | const lots = getAllLotsFromHierarchical(jobOrderData); | ||||
| @@ -1156,8 +1162,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList, printerCo | |||||
| .toUpperCase() | .toUpperCase() | ||||
| .replace(/\s/g, ""); | .replace(/\s/g, ""); | ||||
| const floorKey = storeId.replace(/\//g, ""); | const floorKey = storeId.replace(/\//g, ""); | ||||
| if (floorKey === "2F") return "Label機 2F A+B"; | |||||
| if (floorKey === "4F") return "Label機 4F 乾貨 C, D"; | |||||
| if (floorKey === FLOOR_A_KEY) return LABEL_A; | |||||
| if (floorKey === FLOOR_B_KEY) return LABEL_B; | |||||
| return undefined; | return undefined; | ||||
| }, [workbenchStoreId]); | }, [workbenchStoreId]); | ||||
| @@ -3905,7 +3911,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList, printerCo | |||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| size="small" | size="small" | ||||
| onClick={() => handlePickRecord("2F")} | |||||
| onClick={() => handlePickRecord(FLOOR_A_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 2F | {t("Print Pick Record")} 2F | ||||
| </Button> | </Button> | ||||
| @@ -3913,7 +3919,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList, printerCo | |||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| size="small" | size="small" | ||||
| onClick={() => handlePickRecord("3F")} | |||||
| onClick={() => handlePickRecord(FLOOR_D_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 3F | {t("Print Pick Record")} 3F | ||||
| </Button> | </Button> | ||||
| @@ -3921,7 +3927,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList, printerCo | |||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| size="small" | size="small" | ||||
| onClick={() => handlePickRecord("4F")} | |||||
| onClick={() => handlePickRecord(FLOOR_B_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 4F | {t("Print Pick Record")} 4F | ||||
| </Button> | </Button> | ||||
| @@ -1,5 +1,6 @@ | |||||
| /* | /* | ||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import React, { useCallback, useEffect, useState } from "react"; | import React, { useCallback, useEffect, useState } from "react"; | ||||
| import { | import { | ||||
| Box, | Box, | ||||
| @@ -28,7 +29,7 @@ const JoPickOrderList: React.FC<Props> = ({ onSwitchToRecordTab }) =>{ | |||||
| const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined); | const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined); | ||||
| type PickOrderFilter = "all" | "drink" | "Powder_Mixture" | "other"; | type PickOrderFilter = "all" | "drink" | "Powder_Mixture" | "other"; | ||||
| const [filter, setFilter] = useState<PickOrderFilter>("all"); | const [filter, setFilter] = useState<PickOrderFilter>("all"); | ||||
| type FloorFilter = "ALL" | "2F" | "3F" | "4F" | "NO_LOT"; | |||||
| type FloorFilter = "ALL" | typeof FLOOR_A_KEY | typeof FLOOR_D_KEY | typeof FLOOR_B_KEY | "NO_LOT"; | |||||
| const [floorFilter, setFloorFilter] = useState<FloorFilter>("ALL"); | const [floorFilter, setFloorFilter] = useState<FloorFilter>("ALL"); | ||||
| const fetchPickOrders = useCallback(async () => { | const fetchPickOrders = useCallback(async () => { | ||||
| setLoading(true); | setLoading(true); | ||||
| @@ -124,25 +125,25 @@ const JoPickOrderList: React.FC<Props> = ({ onSwitchToRecordTab }) =>{ | |||||
| {t("Select All")} | {t("Select All")} | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant={floorFilter === "2F" ? "contained" : "outlined"} | |||||
| variant={floorFilter === FLOOR_A_KEY ? "contained" : "outlined"} | |||||
| size="small" | size="small" | ||||
| onClick={() => setFloorFilter("2F")} | |||||
| onClick={() => setFloorFilter(FLOOR_A_KEY)} | |||||
| > | > | ||||
| 2F | |||||
| {FLOOR_A_KEY} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant={floorFilter === "3F" ? "contained" : "outlined"} | |||||
| variant={floorFilter === FLOOR_D_KEY ? "contained" : "outlined"} | |||||
| size="small" | size="small" | ||||
| onClick={() => setFloorFilter("3F")} | |||||
| onClick={() => setFloorFilter(FLOOR_D_KEY)} | |||||
| > | > | ||||
| 3F | 3F | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant={floorFilter === "4F" ? "contained" : "outlined"} | |||||
| variant={floorFilter === FLOOR_B_KEY ? "contained" : "outlined"} | |||||
| size="small" | size="small" | ||||
| onClick={() => setFloorFilter("4F")} | |||||
| onClick={() => setFloorFilter(FLOOR_B_KEY)} | |||||
| > | > | ||||
| 4F | |||||
| {FLOOR_B_KEY} | |||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant={floorFilter === "NO_LOT" ? "contained" : "outlined"} | variant={floorFilter === "NO_LOT" ? "contained" : "outlined"} | ||||
| @@ -1,4 +1,5 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { PickOrderResult } from "@/app/api/pickOrder"; | import { PickOrderResult } from "@/app/api/pickOrder"; | ||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| @@ -165,7 +166,7 @@ const JodetailSearch: React.FC<Props> = ({ printerCombo }) => { | |||||
| } | } | ||||
| }, [tabIndex]); | }, [tabIndex]); | ||||
| const handleAssignByStore = async (storeId: "2/F" | "4/F") => { | |||||
| const handleAssignByStore = async (storeId: typeof FLOOR_A | typeof FLOOR_B) => { | |||||
| if (!currentUserId) { | if (!currentUserId) { | ||||
| console.error("Missing user id in session"); | console.error("Missing user id in session"); | ||||
| return; | return; | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_B, FLOOR_D } from "@/config/demoFloors"; | |||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
| import { | import { | ||||
| Alert, | Alert, | ||||
| @@ -31,7 +32,7 @@ import SafeApexCharts from "@/components/charts/SafeApexCharts"; | |||||
| /** Day.js has no typed `dayjs.invalid()` factory; Invalid Date parses to an invalid Dayjs instance. */ | /** Day.js has no typed `dayjs.invalid()` factory; Invalid Date parses to an invalid Dayjs instance. */ | ||||
| const DAYJS_INVALID = dayjs(new Date(Number.NaN)); | const DAYJS_INVALID = dayjs(new Date(Number.NaN)); | ||||
| type FloorFilter = "all" | "2/F" | "3/F" | "4/F"; | |||||
| type FloorFilter = "all" | typeof FLOOR_A | typeof FLOOR_D | typeof FLOOR_B; | |||||
| type DailySummaryRow = { | type DailySummaryRow = { | ||||
| date: string; | date: string; | ||||
| @@ -81,21 +82,21 @@ const addRecordQty = ( | |||||
| const q3 = Number(record.plasticBoxCartonQty3f ?? 0); | const q3 = Number(record.plasticBoxCartonQty3f ?? 0); | ||||
| const q4 = Number(record.plasticBoxCartonQty4f ?? 0); | const q4 = Number(record.plasticBoxCartonQty4f ?? 0); | ||||
| if (selectedFloor === "all" || selectedFloor === "2/F") { | |||||
| if (selectedFloor === "all" || selectedFloor === FLOOR_A) { | |||||
| current.floor2F += q2; | current.floor2F += q2; | ||||
| } | } | ||||
| if (selectedFloor === "all" || selectedFloor === "3/F") { | |||||
| if (selectedFloor === "all" || selectedFloor === FLOOR_D) { | |||||
| current.floor3F += q3; | current.floor3F += q3; | ||||
| } | } | ||||
| if (selectedFloor === "all" || selectedFloor === "4/F") { | |||||
| if (selectedFloor === "all" || selectedFloor === FLOOR_B) { | |||||
| current.floor4F += q4; | current.floor4F += q4; | ||||
| } | } | ||||
| if (selectedFloor === "all") { | if (selectedFloor === "all") { | ||||
| current.total += q2 + q3 + q4; | current.total += q2 + q3 + q4; | ||||
| } else if (selectedFloor === "2/F") { | |||||
| } else if (selectedFloor === FLOOR_A) { | |||||
| current.total += q2; | current.total += q2; | ||||
| } else if (selectedFloor === "3/F") { | |||||
| } else if (selectedFloor === FLOOR_D) { | |||||
| current.total += q3; | current.total += q3; | ||||
| } else { | } else { | ||||
| current.total += q4; | current.total += q4; | ||||
| @@ -198,9 +199,9 @@ const PlasticBoxCartonDashboardTab: React.FC = () => { | |||||
| const chartSeries = useMemo( | const chartSeries = useMemo( | ||||
| () => [ | () => [ | ||||
| { name: "2/F", data: rows.map((row) => row.floor2F) }, | |||||
| { name: "3/F", data: rows.map((row) => row.floor3F) }, | |||||
| { name: "4/F", data: rows.map((row) => row.floor4F) }, | |||||
| { name: FLOOR_A, data: rows.map((row) => row.floor2F) }, | |||||
| { name: FLOOR_D, data: rows.map((row) => row.floor3F) }, | |||||
| { name: FLOOR_B, data: rows.map((row) => row.floor4F) }, | |||||
| { name: t("Total plastic box carton qty"), data: rows.map((row) => row.total) }, | { name: t("Total plastic box carton qty"), data: rows.map((row) => row.total) }, | ||||
| ], | ], | ||||
| [rows, t], | [rows, t], | ||||
| @@ -351,17 +352,17 @@ const PlasticBoxCartonDashboardTab: React.FC = () => { | |||||
| ["", "", "", "", ""], | ["", "", "", "", ""], | ||||
| [ | [ | ||||
| t("Date"), | t("Date"), | ||||
| t("2/F plastic box carton qty count"), | |||||
| t("3/F plastic box carton qty count"), | |||||
| t("4/F plastic box carton qty count"), | |||||
| t(FLOOR_A + " plastic box carton qty count"), | |||||
| t(FLOOR_D + " plastic box carton qty count"), | |||||
| t(FLOOR_B + " plastic box carton qty count"), | |||||
| t("Total plastic box carton qty"), | t("Total plastic box carton qty"), | ||||
| ], | ], | ||||
| ...dailyRows.map((row) => [row.date, row.floor2F, row.floor3F, row.floor4F, row.total]), | ...dailyRows.map((row) => [row.date, row.floor2F, row.floor3F, row.floor4F, row.total]), | ||||
| ["", "", "", "", ""], | ["", "", "", "", ""], | ||||
| [t("Summary"), "", "", "", ""], | [t("Summary"), "", "", "", ""], | ||||
| [t("2/F plastic box carton qty count"), reportSummary.floor2F, "", "", ""], | |||||
| [t("3/F plastic box carton qty count"), reportSummary.floor3F, "", "", ""], | |||||
| [t("4/F plastic box carton qty count"), reportSummary.floor4F, "", "", ""], | |||||
| [t(FLOOR_A + " plastic box carton qty count"), reportSummary.floor2F, "", "", ""], | |||||
| [t(FLOOR_D + " plastic box carton qty count"), reportSummary.floor3F, "", "", ""], | |||||
| [t(FLOOR_B + " plastic box carton qty count"), reportSummary.floor4F, "", "", ""], | |||||
| [t("Total plastic box carton qty"), reportSummary.total, "", "", ""], | [t("Total plastic box carton qty"), reportSummary.total, "", "", ""], | ||||
| ]; | ]; | ||||
| @@ -469,9 +470,9 @@ const PlasticBoxCartonDashboardTab: React.FC = () => { | |||||
| onChange={(event) => setFloor(event.target.value as FloorFilter)} | onChange={(event) => setFloor(event.target.value as FloorFilter)} | ||||
| > | > | ||||
| <MenuItem value="all">{t("All")}</MenuItem> | <MenuItem value="all">{t("All")}</MenuItem> | ||||
| <MenuItem value="2/F">2/F</MenuItem> | |||||
| <MenuItem value="3/F">3/F</MenuItem> | |||||
| <MenuItem value="4/F">4/F</MenuItem> | |||||
| <MenuItem value={FLOOR_A}>{FLOOR_A}</MenuItem> | |||||
| <MenuItem value={FLOOR_D}>{FLOOR_D}</MenuItem> | |||||
| <MenuItem value={FLOOR_B}>{FLOOR_B}</MenuItem> | |||||
| </TextField> | </TextField> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={6}> | <Grid item xs={12} md={6}> | ||||
| @@ -492,19 +493,19 @@ const PlasticBoxCartonDashboardTab: React.FC = () => { | |||||
| <Table size="small"> | <Table size="small"> | ||||
| <TableBody> | <TableBody> | ||||
| <TableRow> | <TableRow> | ||||
| <TableCell>{t("2/F plastic box carton qty count")}</TableCell> | |||||
| <TableCell>{t(FLOOR_A + " plastic box carton qty count")}</TableCell> | |||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| {summary.floor2F.toLocaleString("zh-HK")} | {summary.floor2F.toLocaleString("zh-HK")} | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| <TableRow> | <TableRow> | ||||
| <TableCell>{t("3/F plastic box carton qty count")}</TableCell> | |||||
| <TableCell>{t(FLOOR_D + " plastic box carton qty count")}</TableCell> | |||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| {summary.floor3F.toLocaleString("zh-HK")} | {summary.floor3F.toLocaleString("zh-HK")} | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| <TableRow> | <TableRow> | ||||
| <TableCell>{t("4/F plastic box carton qty count")}</TableCell> | |||||
| <TableCell>{t(FLOOR_B + " plastic box carton qty count")}</TableCell> | |||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| {summary.floor4F.toLocaleString("zh-HK")} | {summary.floor4F.toLocaleString("zh-HK")} | ||||
| </TableCell> | </TableCell> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -774,21 +775,21 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => handlePickRecord(jobOrderPickOrder, "2F")} | |||||
| onClick={() => handlePickRecord(jobOrderPickOrder, FLOOR_A_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 2F | {t("Print Pick Record")} 2F | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => handlePickRecord(jobOrderPickOrder, "3F")} | |||||
| onClick={() => handlePickRecord(jobOrderPickOrder, FLOOR_D_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 3F | {t("Print Pick Record")} 3F | ||||
| </Button> | </Button> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| onClick={() => handlePickRecord(jobOrderPickOrder, "4F")} | |||||
| onClick={() => handlePickRecord(jobOrderPickOrder, FLOOR_B_KEY)} | |||||
| > | > | ||||
| {t("Print Pick Record")} 4F | {t("Print Pick Record")} 4F | ||||
| </Button> | </Button> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_C_KEY, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -684,15 +685,22 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| return allLots; | return allLots; | ||||
| }, []); | }, []); | ||||
| const extractFloor = (lot: any): string => { | const extractFloor = (lot: any): string => { | ||||
| const raw = lot.routerRoute || lot.routerArea || lot.location || ''; | |||||
| const match = raw.match(/^(\d+F?)/i) || raw.split('-')[0]; | |||||
| return (match?.[1] || match || raw || '').toUpperCase().replace(/(\d)F?/i, '$1F'); | |||||
| const raw = String(lot.routerRoute || lot.routerArea || lot.location || ""); | |||||
| const head = raw.trim().toUpperCase().replace(/\//g, "").split("-")[0]; | |||||
| if (head === "2" || head === FLOOR_A_KEY) return FLOOR_A_KEY; | |||||
| if (head === "4" || head === FLOOR_B_KEY) return FLOOR_B_KEY; | |||||
| if (head === "3" || head === FLOOR_D_KEY) return FLOOR_D_KEY; | |||||
| if (head === "1" || head === FLOOR_C_KEY) return FLOOR_C_KEY; | |||||
| return head; | |||||
| }; | }; | ||||
| // 楼层排序权重:4F > 3F > 2F(数字越大越靠前) | |||||
| // B/F, then D/F, then A/F. | |||||
| const floorSortOrder = (floor: string): number => { | const floorSortOrder = (floor: string): number => { | ||||
| const n = parseInt(floor.replace(/\D/g, ''), 10); | |||||
| return isNaN(n) ? 0 : n; | |||||
| if (floor === FLOOR_B_KEY) return 4; | |||||
| if (floor === FLOOR_D_KEY) return 3; | |||||
| if (floor === FLOOR_A_KEY) return 2; | |||||
| if (floor === FLOOR_C_KEY) return 1; | |||||
| return 0; | |||||
| }; | }; | ||||
| const combinedLotData = useMemo(() => { | const combinedLotData = useMemo(() => { | ||||
| const lots = getAllLotsFromHierarchical(jobOrderData); | const lots = getAllLotsFromHierarchical(jobOrderData); | ||||
| @@ -2613,7 +2621,7 @@ const sortedData = [...sourceData].sort((a, b) => { | |||||
| const floorB = extractFloor(b); | const floorB = extractFloor(b); | ||||
| const orderA = floorSortOrder(floorA); | const orderA = floorSortOrder(floorA); | ||||
| const orderB = floorSortOrder(floorB); | const orderB = floorSortOrder(floorB); | ||||
| if (orderA !== orderB) return orderB - orderA; // 4F, 3F, 2F | |||||
| if (orderA !== orderB) return orderB - orderA; // ${FLOOR_B_KEY}, ${FLOOR_D_KEY}, ${FLOOR_A_KEY} | |||||
| // 同楼层再按 routerIndex、pickOrderCode、lotNo | // 同楼层再按 routerIndex、pickOrderCode、lotNo | ||||
| const aIndex = a.routerIndex ?? 0; | const aIndex = a.routerIndex ?? 0; | ||||
| const bIndex = b.routerIndex ?? 0; | const bIndex = b.routerIndex ?? 0; | ||||
| @@ -1,10 +1,11 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A, FLOOR_A_KEY, FLOOR_B, FLOOR_B_KEY, FLOOR_D, FLOOR_D_KEY } from "@/config/demoFloors"; | |||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import type { TFunction } from "i18next"; | import type { TFunction } from "i18next"; | ||||
| import { fetchPickRecordPlasticBoxCartonQty } from "@/app/api/jo/actions"; | import { fetchPickRecordPlasticBoxCartonQty } from "@/app/api/jo/actions"; | ||||
| export type PickRecordFloor = "2F" | "3F" | "4F" | "ALL"; | |||||
| export type PickRecordFloor = typeof FLOOR_A_KEY | typeof FLOOR_D_KEY | typeof FLOOR_B_KEY | "ALL"; | |||||
| export interface AllFloorsPlasticBoxCartonQtyResult { | export interface AllFloorsPlasticBoxCartonQtyResult { | ||||
| qty2f: number; | qty2f: number; | ||||
| @@ -115,9 +116,9 @@ export async function promptAllFloorsPlasticBoxCartonQty( | |||||
| title: t("Enter all floors plastic box carton qty"), | title: t("Enter all floors plastic box carton qty"), | ||||
| html: ` | html: ` | ||||
| <div style="display:flex;flex-direction:column;gap:10px;text-align:left;"> | <div style="display:flex;flex-direction:column;gap:10px;text-align:left;"> | ||||
| ${buildFloorQtyInputRow("swal-qty-2f", t("2/F plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty2f)} | |||||
| ${buildFloorQtyInputRow("swal-qty-3f", t("3/F plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty3f)} | |||||
| ${buildFloorQtyInputRow("swal-qty-4f", t("4/F plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty4f)} | |||||
| ${buildFloorQtyInputRow("swal-qty-2f", t(FLOOR_A + " plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty2f)} | |||||
| ${buildFloorQtyInputRow("swal-qty-3f", t(FLOOR_D + " plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty3f)} | |||||
| ${buildFloorQtyInputRow("swal-qty-4f", t(FLOOR_B + " plastic box carton Qty"), placeholder, existing.plasticBoxCartonQty4f)} | |||||
| </div> | </div> | ||||
| `, | `, | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY, FLOOR_C_KEY, FLOOR_D_KEY, LOC_HOLD, LOC_WIP } from "@/config/demoFloors"; | |||||
| import React, { useState } from "react"; | import React, { useState } from "react"; | ||||
| import InfoOutlined from "@mui/icons-material/InfoOutlined"; | import InfoOutlined from "@mui/icons-material/InfoOutlined"; | ||||
| import { | import { | ||||
| @@ -33,17 +34,17 @@ const sections: { title: string; lines: string[] }[] = [ | |||||
| { | { | ||||
| title: "送貨揀貨的樓層", | title: "送貨揀貨的樓層", | ||||
| lines: [ | lines: [ | ||||
| "票上已指定 2F 或 4F:只建議該樓層的批次。", | |||||
| "沒有指定樓層:供應商在「4F 供應商」清單就用 4F,否則用 2F。這兩份清單可以在「送貨單樓層(供應商)」修改。", | |||||
| "放單時若供應商在 2F 或 4F 清單內,只建議該樓層;1F 的批次仍然可以建議。不在清單內的供應商不限制 2F / 4F。", | |||||
| "送貨單不建議 3F。", | |||||
| "票上已指定 " + FLOOR_A_KEY + " 或 " + FLOOR_B_KEY + ":只建議該樓層的批次。", | |||||
| "沒有指定樓層:供應商在「" + FLOOR_B_KEY + " 供應商」清單就用 " + FLOOR_B_KEY + ",否則用 " + FLOOR_A_KEY + "。這兩份清單可以在「送貨單樓層(供應商)」修改。", | |||||
| "放單時若供應商在 " + FLOOR_A_KEY + " 或 " + FLOOR_B_KEY + " 清單內,只建議該樓層;" + FLOOR_C_KEY + " 的批次仍然可以建議。不在清單內的供應商不限制 " + FLOOR_A_KEY + " / " + FLOOR_B_KEY + "。", | |||||
| "送貨單不建議 " + FLOOR_D_KEY + "。", | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| title: "目前不建議的倉位", | title: "目前不建議的倉位", | ||||
| lines: [ | lines: [ | ||||
| "2F-W202-01-00", | |||||
| "2F-W200-#A-00", | |||||
| LOC_HOLD, | |||||
| LOC_WIP, | |||||
| "這兩個倉位寫在程式裡,畫面還不能改。", | "這兩個倉位寫在程式裡,畫面還不能改。", | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -51,8 +52,8 @@ const sections: { title: string; lines: string[] }[] = [ | |||||
| title: "工單提料(和送貨揀貨不同)", | title: "工單提料(和送貨揀貨不同)", | ||||
| lines: [ | lines: [ | ||||
| "同樣是先到期先出。", | "同樣是先到期先出。", | ||||
| "不建議 2F-W202-01-00。半成品會先試 2F-W200-#A-00。", | |||||
| "工單可以使用 3F。", | |||||
| "不建議 " + LOC_HOLD + "。半成品會先試 " + LOC_WIP + "。", | |||||
| "工單可以使用 " + FLOOR_D_KEY + "。", | |||||
| ], | ], | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import React, { | import React, { | ||||
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| @@ -260,7 +261,7 @@ function summarizeLogisticsColumnStats(lanes: Lane[]): { | |||||
| let count4F = 0; | let count4F = 0; | ||||
| for (const l of lanes) { | for (const l of lanes) { | ||||
| shopCount += l.shops.length; | shopCount += l.shops.length; | ||||
| if (normalizeStoreId(l.storeId) === "4F") count4F++; | |||||
| if (normalizeStoreId(l.storeId) === FLOOR_B_KEY) count4F++; | |||||
| else count2F++; | else count2F++; | ||||
| } | } | ||||
| return { | return { | ||||
| @@ -516,7 +517,7 @@ function buildLaneFromPendingNewLane(p: PendingNewLane): Lane { | |||||
| const code = String(payload.truckLanceCode || "").trim(); | const code = String(payload.truckLanceCode || "").trim(); | ||||
| const storeNorm = normalizeStoreId(payload.store_id); | const storeNorm = normalizeStoreId(payload.store_id); | ||||
| const remarkRaw = | const remarkRaw = | ||||
| storeNorm === "4F" && | |||||
| storeNorm === FLOOR_B_KEY && | |||||
| payload.remark != null && | payload.remark != null && | ||||
| String(payload.remark).trim() !== "" | String(payload.remark).trim() !== "" | ||||
| ? String(payload.remark).trim() | ? String(payload.remark).trim() | ||||
| @@ -690,7 +691,7 @@ function buildLaneFromTruckRows( | |||||
| phone: lanePhone, | phone: lanePhone, | ||||
| plate: lanePlate, | plate: lanePlate, | ||||
| startTime: startTime || "00:00:00", | startTime: startTime || "00:00:00", | ||||
| storeId: storeId || "2F", | |||||
| storeId: storeId || FLOOR_A_KEY, | |||||
| remark, | remark, | ||||
| shops, | shops, | ||||
| }; | }; | ||||
| @@ -853,7 +854,7 @@ const RouteBoard: React.FC = () => { | |||||
| "board", | "board", | ||||
| ); | ); | ||||
| const [laneFilter, setLaneFilter] = useState<{ | const [laneFilter, setLaneFilter] = useState<{ | ||||
| floor: "all" | "2F" | "4F"; | |||||
| floor: "all" | typeof FLOOR_A_KEY | typeof FLOOR_B_KEY; | |||||
| query: string; | query: string; | ||||
| }>({ | }>({ | ||||
| floor: "all", | floor: "all", | ||||
| @@ -1100,7 +1101,7 @@ const RouteBoard: React.FC = () => { | |||||
| /** 物流主檔 id;null = 未指定(至物流商管理指派) */ | /** 物流主檔 id;null = 未指定(至物流商管理指派) */ | ||||
| logisticId: number | null; | logisticId: number | null; | ||||
| startTime: string; | startTime: string; | ||||
| storeId: "2F" | "4F"; | |||||
| storeId: typeof FLOOR_A_KEY | typeof FLOOR_B_KEY; | |||||
| remark: string; | remark: string; | ||||
| }; | }; | ||||
| @@ -1108,7 +1109,7 @@ const RouteBoard: React.FC = () => { | |||||
| truckLanceCode: "", | truckLanceCode: "", | ||||
| logisticId: null, | logisticId: null, | ||||
| startTime: "07:30", | startTime: "07:30", | ||||
| storeId: "2F", | |||||
| storeId: FLOOR_A_KEY, | |||||
| remark: "", | remark: "", | ||||
| }); | }); | ||||
| @@ -1201,7 +1202,7 @@ const RouteBoard: React.FC = () => { | |||||
| const code = String(form.truckLanceCode || "").trim(); | const code = String(form.truckLanceCode || "").trim(); | ||||
| const storeNorm = normalizeStoreId(form.storeId); | const storeNorm = normalizeStoreId(form.storeId); | ||||
| const remarkRaw = | const remarkRaw = | ||||
| storeNorm === "4F" && String(form.remark || "").trim() !== "" | |||||
| storeNorm === FLOOR_B_KEY && String(form.remark || "").trim() !== "" | |||||
| ? String(form.remark).trim() | ? String(form.remark).trim() | ||||
| : null; | : null; | ||||
| const dep = parseTimeForBackend(form.startTime || "") || "00:00:00"; | const dep = parseTimeForBackend(form.startTime || "") || "00:00:00"; | ||||
| @@ -1244,7 +1245,7 @@ const RouteBoard: React.FC = () => { | |||||
| const code = String(form.truckLanceCode || "").trim(); | const code = String(form.truckLanceCode || "").trim(); | ||||
| const storeNorm = normalizeStoreId(form.storeId); | const storeNorm = normalizeStoreId(form.storeId); | ||||
| const remarkRaw = | const remarkRaw = | ||||
| storeNorm === "4F" && String(form.remark || "").trim() !== "" | |||||
| storeNorm === FLOOR_B_KEY && String(form.remark || "").trim() !== "" | |||||
| ? String(form.remark).trim() | ? String(form.remark).trim() | ||||
| : null; | : null; | ||||
| return { | return { | ||||
| @@ -2296,7 +2297,7 @@ const RouteBoard: React.FC = () => { | |||||
| const code = String(newRouteForm.truckLanceCode || "").trim(); | const code = String(newRouteForm.truckLanceCode || "").trim(); | ||||
| const storeNorm = normalizeStoreId(newRouteForm.storeId); | const storeNorm = normalizeStoreId(newRouteForm.storeId); | ||||
| const remarkRaw = | const remarkRaw = | ||||
| storeNorm === "4F" && String(newRouteForm.remark || "").trim() !== "" | |||||
| storeNorm === FLOOR_B_KEY && String(newRouteForm.remark || "").trim() !== "" | |||||
| ? String(newRouteForm.remark).trim() | ? String(newRouteForm.remark).trim() | ||||
| : null; | : null; | ||||
| const laneKey = encodeLaneId(code, remarkRaw); | const laneKey = encodeLaneId(code, remarkRaw); | ||||
| @@ -2370,7 +2371,7 @@ const RouteBoard: React.FC = () => { | |||||
| const seq = Number(newSeq) || 0; | const seq = Number(newSeq) || 0; | ||||
| const storeId = normalizeStoreId(lane.storeId); | const storeId = normalizeStoreId(lane.storeId); | ||||
| const remark = | const remark = | ||||
| storeId === "4F" && | |||||
| storeId === FLOOR_B_KEY && | |||||
| lane.remark != null && | lane.remark != null && | ||||
| String(lane.remark).trim() !== "" | String(lane.remark).trim() !== "" | ||||
| ? String(lane.remark).trim() | ? String(lane.remark).trim() | ||||
| @@ -2678,7 +2679,7 @@ const RouteBoard: React.FC = () => { | |||||
| departureTime: to.startTime, | departureTime: to.startTime, | ||||
| storeId: to.storeId, | storeId: to.storeId, | ||||
| remark: | remark: | ||||
| normalizeStoreId(to.storeId) === "4F" | |||||
| normalizeStoreId(to.storeId) === FLOOR_B_KEY | |||||
| ? to.remark != null && String(to.remark).trim() !== "" | ? to.remark != null && String(to.remark).trim() !== "" | ||||
| ? String(to.remark).trim() | ? String(to.remark).trim() | ||||
| : null | : null | ||||
| @@ -2713,7 +2714,7 @@ const RouteBoard: React.FC = () => { | |||||
| to.shops = inserted; | to.shops = inserted; | ||||
| // IMPORTANT: Do NOT renumber all loadingSequence. | // IMPORTANT: Do NOT renumber all loadingSequence. | ||||
| // loadingSequence can be duplicated intentionally (e.g. 4F grouping). | |||||
| // loadingSequence can be duplicated intentionally (e.g. ${FLOOR_B_KEY} grouping). | |||||
| // On drag/drop, only update the moved shop's loadingSequence so it joins the target group. | // On drag/drop, only update the moved shop's loadingSequence so it joins the target group. | ||||
| const newSeq = computeMovedLoadingSequence( | const newSeq = computeMovedLoadingSequence( | ||||
| inserted, | inserted, | ||||
| @@ -3356,7 +3357,7 @@ const RouteBoard: React.FC = () => { | |||||
| if (!lane) continue; | if (!lane) continue; | ||||
| const sid = normalizeStoreId(lane.storeId); | const sid = normalizeStoreId(lane.storeId); | ||||
| const remark = | const remark = | ||||
| sid === "4F" && | |||||
| sid === FLOOR_B_KEY && | |||||
| lane.remark != null && | lane.remark != null && | ||||
| String(lane.remark).trim() !== "" | String(lane.remark).trim() !== "" | ||||
| ? String(lane.remark).trim() | ? String(lane.remark).trim() | ||||
| @@ -6085,15 +6086,15 @@ const RouteBoard: React.FC = () => { | |||||
| onChange={(e) => | onChange={(e) => | ||||
| setNewRouteForm((p) => ({ | setNewRouteForm((p) => ({ | ||||
| ...p, | ...p, | ||||
| storeId: e.target.value as "2F" | "4F", | |||||
| storeId: e.target.value as typeof FLOOR_A_KEY | typeof FLOOR_B_KEY, | |||||
| })) | })) | ||||
| } | } | ||||
| > | > | ||||
| <MenuItem value="2F">2F</MenuItem> | |||||
| <MenuItem value="4F">4F</MenuItem> | |||||
| <MenuItem value={FLOOR_A_KEY}>{FLOOR_A_KEY}</MenuItem> | |||||
| <MenuItem value={FLOOR_B_KEY}>{FLOOR_B_KEY}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| {newRouteForm.storeId === "4F" && ( | |||||
| {newRouteForm.storeId === FLOOR_B_KEY && ( | |||||
| <TextField | <TextField | ||||
| size="small" | size="small" | ||||
| fullWidth | fullWidth | ||||
| @@ -6683,8 +6684,8 @@ const RouteBoard: React.FC = () => { | |||||
| } | } | ||||
| > | > | ||||
| <MenuItem value="all">{t("floor_all")}</MenuItem> | <MenuItem value="all">{t("floor_all")}</MenuItem> | ||||
| <MenuItem value="2F">2F</MenuItem> | |||||
| <MenuItem value="4F">4F</MenuItem> | |||||
| <MenuItem value={FLOOR_A_KEY}>{FLOOR_A_KEY}</MenuItem> | |||||
| <MenuItem value={FLOOR_B_KEY}>{FLOOR_B_KEY}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Stack | <Stack | ||||
| @@ -7263,7 +7264,7 @@ const RouteBoard: React.FC = () => { | |||||
| /> | /> | ||||
| <Chip | <Chip | ||||
| size="small" | size="small" | ||||
| label={`2F · ${colStats.count2F}`} | |||||
| label={`${FLOOR_A_KEY} · ${colStats.count2F}`} | |||||
| sx={{ | sx={{ | ||||
| height: 24, | height: 24, | ||||
| fontWeight: 700, | fontWeight: 700, | ||||
| @@ -7272,7 +7273,7 @@ const RouteBoard: React.FC = () => { | |||||
| /> | /> | ||||
| <Chip | <Chip | ||||
| size="small" | size="small" | ||||
| label={`4F · ${colStats.count4F}`} | |||||
| label={`${FLOOR_B_KEY} · ${colStats.count4F}`} | |||||
| sx={{ | sx={{ | ||||
| height: 24, | height: 24, | ||||
| fontWeight: 700, | fontWeight: 700, | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -134,14 +135,14 @@ const Shop: React.FC = () => { | |||||
| const loadingSeqNum = loadingSeq != null && loadingSeq !== undefined ? Number(loadingSeq) : null; | const loadingSeqNum = loadingSeq != null && loadingSeq !== undefined ? Number(loadingSeq) : null; | ||||
| const hasLoadingSequence = loadingSeqNum !== null && !isNaN(loadingSeqNum) && loadingSeqNum !== 0; | const hasLoadingSequence = loadingSeqNum !== null && !isNaN(loadingSeqNum) && loadingSeqNum !== 0; | ||||
| // Check storeId: must exist and not be 0 (can be string "2F"/"4F" or number) | |||||
| // Check storeId: must exist and not be 0 (can be string FLOOR_A_KEY/FLOOR_B_KEY or number) | |||||
| // Actual field name in JSON is store_id (underscore, lowercase) | // Actual field name in JSON is store_id (underscore, lowercase) | ||||
| const storeId = (truck as any).store_id || (truck as any).storeId || (truck as any).Store_id; | const storeId = (truck as any).store_id || (truck as any).storeId || (truck as any).Store_id; | ||||
| let storeIdValid = false; | let storeIdValid = false; | ||||
| if (storeId != null && storeId !== undefined && storeId !== "") { | if (storeId != null && storeId !== undefined && storeId !== "") { | ||||
| const storeIdStr = String(storeId).trim(); | const storeIdStr = String(storeId).trim(); | ||||
| // If it's "2F" or "4F", it's valid (not 0) | |||||
| if (storeIdStr === "2F" || storeIdStr === "4F") { | |||||
| // If it's FLOOR_A_KEY or FLOOR_B_KEY, it's valid (not 0) | |||||
| if (storeIdStr === FLOOR_A_KEY || storeIdStr === FLOOR_B_KEY) { | |||||
| storeIdValid = true; | storeIdValid = true; | ||||
| } else { | } else { | ||||
| const storeIdNum = Number(storeId); | const storeIdNum = Number(storeId); | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -274,9 +275,9 @@ const ShopDetail: React.FC = () => { | |||||
| } | } | ||||
| const lane = selectedTruckLane!; | const lane = selectedTruckLane!; | ||||
| const storeIdStr = normalizeStoreId(lane.storeId) || "2F"; | |||||
| const storeIdStr = normalizeStoreId(lane.storeId) || FLOOR_A_KEY; | |||||
| const remarkValue = | const remarkValue = | ||||
| storeIdStr === "4F" && lane.remark != null && String(lane.remark).trim() !== "" | |||||
| storeIdStr === FLOOR_B_KEY && lane.remark != null && String(lane.remark).trim() !== "" | |||||
| ? String(lane.remark).trim() | ? String(lane.remark).trim() | ||||
| : null; | : null; | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -56,7 +57,7 @@ const TruckLane: React.FC = () => { | |||||
| const [newTruck, setNewTruck] = useState({ | const [newTruck, setNewTruck] = useState({ | ||||
| truckLanceCode: "", | truckLanceCode: "", | ||||
| departureTime: "", | departureTime: "", | ||||
| storeId: "2F", | |||||
| storeId: FLOOR_A_KEY, | |||||
| }); | }); | ||||
| const [saving, setSaving] = useState<boolean>(false); | const [saving, setSaving] = useState<boolean>(false); | ||||
| const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false); | const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false); | ||||
| @@ -192,7 +193,7 @@ const TruckLane: React.FC = () => { | |||||
| setNewTruck({ | setNewTruck({ | ||||
| truckLanceCode: "", | truckLanceCode: "", | ||||
| departureTime: "", | departureTime: "", | ||||
| storeId: "2F", | |||||
| storeId: FLOOR_A_KEY, | |||||
| }); | }); | ||||
| setAddDialogOpen(true); | setAddDialogOpen(true); | ||||
| setError(null); | setError(null); | ||||
| @@ -203,7 +204,7 @@ const TruckLane: React.FC = () => { | |||||
| setNewTruck({ | setNewTruck({ | ||||
| truckLanceCode: "", | truckLanceCode: "", | ||||
| departureTime: "", | departureTime: "", | ||||
| storeId: "2F", | |||||
| storeId: FLOOR_A_KEY, | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -373,8 +374,8 @@ const TruckLane: React.FC = () => { | |||||
| }} | }} | ||||
| disabled={saving} | disabled={saving} | ||||
| > | > | ||||
| <MenuItem value="2F">2F</MenuItem> | |||||
| <MenuItem value="4F">4F</MenuItem> | |||||
| <MenuItem value={FLOOR_A_KEY}>{FLOOR_A_KEY}</MenuItem> | |||||
| <MenuItem value={FLOOR_B_KEY}>{FLOOR_B_KEY}</MenuItem> | |||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -291,9 +292,9 @@ const TruckLaneDetail: React.FC = () => { | |||||
| await Promise.all( | await Promise.all( | ||||
| withIds.map((row) => { | withIds.map((row) => { | ||||
| const storeIdStr = normalizeStoreId(row.storeId) || "2F"; | |||||
| const storeIdStr = normalizeStoreId(row.storeId) || FLOOR_A_KEY; | |||||
| let remarkValue: string | null = null; | let remarkValue: string | null = null; | ||||
| if (storeIdStr === "4F" && row.remark != null && String(row.remark).trim() !== "") { | |||||
| if (storeIdStr === FLOOR_B_KEY && row.remark != null && String(row.remark).trim() !== "") { | |||||
| remarkValue = String(row.remark).trim(); | remarkValue = String(row.remark).trim(); | ||||
| } | } | ||||
| return updateTruckLaneClient({ | return updateTruckLaneClient({ | ||||
| @@ -579,8 +580,8 @@ const TruckLaneDetail: React.FC = () => { | |||||
| try { | try { | ||||
| const storeIdValue = truckData.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String | const storeIdValue = truckData.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String | ||||
| ? String(truckData.storeId) | ? String(truckData.storeId) | ||||
| : String(truckData.storeId)) : "2F"; | |||||
| const displayStoreId = normalizeStoreId(storeIdValue) || "2F"; | |||||
| : String(truckData.storeId)) : FLOOR_A_KEY; | |||||
| const displayStoreId = normalizeStoreId(storeIdValue) || FLOOR_A_KEY; | |||||
| let departureTimeStr = ""; | let departureTimeStr = ""; | ||||
| if (truckData.departureTime) { | if (truckData.departureTime) { | ||||
| @@ -597,8 +598,8 @@ const TruckLaneDetail: React.FC = () => { | |||||
| ); | ); | ||||
| const shopIdValue = foundShop ? foundShop.id : null; | const shopIdValue = foundShop ? foundShop.id : null; | ||||
| // Get remark - only if storeId is "4F" | |||||
| const remarkValue = displayStoreId === "4F" ? (newShop.remark?.trim() || null) : null; | |||||
| // Get remark - only if storeId is FLOOR_B_KEY | |||||
| const remarkValue = displayStoreId === FLOOR_B_KEY ? (newShop.remark?.trim() || null) : null; | |||||
| // Check if there's an "Unassign" row for this truck lane that should be replaced | // Check if there's an "Unassign" row for this truck lane that should be replaced | ||||
| let unassignTruck: Truck | null = null; | let unassignTruck: Truck | null = null; | ||||
| @@ -909,7 +910,7 @@ const TruckLaneDetail: React.FC = () => { | |||||
| {(editingRowIndex === index || massEditing) ? ( | {(editingRowIndex === index || massEditing) ? ( | ||||
| (() => { | (() => { | ||||
| const storeIdValue = truckData.storeId ? String(truckData.storeId) : null; | const storeIdValue = truckData.storeId ? String(truckData.storeId) : null; | ||||
| const isEditable = normalizeStoreId(storeIdValue) === "4F"; | |||||
| const isEditable = normalizeStoreId(storeIdValue) === FLOOR_B_KEY; | |||||
| return ( | return ( | ||||
| <Autocomplete | <Autocomplete | ||||
| freeSolo | freeSolo | ||||
| @@ -1096,7 +1097,7 @@ const TruckLaneDetail: React.FC = () => { | |||||
| const storeIdValue = truckData?.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String | const storeIdValue = truckData?.storeId ? (typeof truckData.storeId === 'string' || truckData.storeId instanceof String | ||||
| ? String(truckData.storeId) | ? String(truckData.storeId) | ||||
| : String(truckData.storeId)) : null; | : String(truckData.storeId)) : null; | ||||
| const isEditable = normalizeStoreId(storeIdValue) === "4F"; | |||||
| const isEditable = normalizeStoreId(storeIdValue) === FLOOR_B_KEY; | |||||
| return isEditable ? ( | return isEditable ? ( | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Autocomplete | <Autocomplete | ||||
| @@ -2,6 +2,7 @@ | |||||
| * 車線看板警示:Rule1(4F 同店跨線 weekday)、Rule2(非 4F 同店跨線發車時分)。 | * 車線看板警示:Rule1(4F 同店跨線 weekday)、Rule2(非 4F 同店跨線發車時分)。 | ||||
| * 規則細節見 `README-ROUTE-BOARD-WARNINGS.md`。 | * 規則細節見 `README-ROUTE-BOARD-WARNINGS.md`。 | ||||
| */ | */ | ||||
| import { FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil"; | import { formatDepartureTime, normalizeStoreId } from "@/app/utils/formatUtil"; | ||||
| const LANE_KEY_SEP = "|"; | const LANE_KEY_SEP = "|"; | ||||
| @@ -168,7 +169,7 @@ function rowToLaneRef(row: TruckLaneWarningInputRow): TruckLaneWarningLaneRef { | |||||
| const storeNorm = normalizeStoreId(row.storeId); | const storeNorm = normalizeStoreId(row.storeId); | ||||
| const depDisp = departureTimeToHHmmKey(row.departureTime); | const depDisp = departureTimeToHHmmKey(row.departureTime); | ||||
| const weekday = | const weekday = | ||||
| storeNorm === "4F" | |||||
| storeNorm === FLOOR_B_KEY | |||||
| ? parseWeekdayFromTruckLanceCode(row.truckLanceCode) | ? parseWeekdayFromTruckLanceCode(row.truckLanceCode) | ||||
| : null; | : null; | ||||
| return { | return { | ||||
| @@ -227,7 +228,7 @@ export function computeTruckLaneWarnings( | |||||
| >(); | >(); | ||||
| for (const row of active) { | for (const row of active) { | ||||
| const store = normalizeStoreId(row.storeId); | const store = normalizeStoreId(row.storeId); | ||||
| if (store !== "4F") continue; | |||||
| if (store !== FLOOR_B_KEY) continue; | |||||
| const wd = parseWeekdayFromTruckLanceCode(row.truckLanceCode); | const wd = parseWeekdayFromTruckLanceCode(row.truckLanceCode); | ||||
| if (!wd) { | if (!wd) { | ||||
| weekdayParseFailures.push({ | weekdayParseFailures.push({ | ||||
| @@ -258,7 +259,7 @@ export function computeTruckLaneWarnings( | |||||
| const sampleRow = active.find( | const sampleRow = active.find( | ||||
| (r) => | (r) => | ||||
| shopKOf(r) === shopK && | shopKOf(r) === shopK && | ||||
| normalizeStoreId(r.storeId) === "4F" && | |||||
| normalizeStoreId(r.storeId) === FLOOR_B_KEY && | |||||
| parseWeekdayFromTruckLanceCode(r.truckLanceCode) === wd, | parseWeekdayFromTruckLanceCode(r.truckLanceCode) === wd, | ||||
| ); | ); | ||||
| const laneKeyList: string[] = Array.from(laneKeys); | const laneKeyList: string[] = Array.from(laneKeys); | ||||
| @@ -278,7 +279,7 @@ export function computeTruckLaneWarnings( | |||||
| const r2Map = new Map<string, Map<string, Set<string>>>(); | const r2Map = new Map<string, Map<string, Set<string>>>(); | ||||
| for (const row of active) { | for (const row of active) { | ||||
| const store = normalizeStoreId(row.storeId); | const store = normalizeStoreId(row.storeId); | ||||
| if (store === "4F") continue; | |||||
| if (store === FLOOR_B_KEY) continue; | |||||
| const hhmm = departureTimeToHHmmKey(row.departureTime); | const hhmm = departureTimeToHHmmKey(row.departureTime); | ||||
| if (!hhmm) continue; | if (!hhmm) continue; | ||||
| const shopK = shopKOf(row); | const shopK = shopKOf(row); | ||||
| @@ -302,7 +303,7 @@ export function computeTruckLaneWarnings( | |||||
| const sampleRow = active.find( | const sampleRow = active.find( | ||||
| (r) => | (r) => | ||||
| shopKOf(r) === shopK && | shopKOf(r) === shopK && | ||||
| normalizeStoreId(r.storeId) !== "4F" && | |||||
| normalizeStoreId(r.storeId) !== FLOOR_B_KEY && | |||||
| departureTimeToHHmmKey(r.departureTime) === hhmm, | departureTimeToHHmmKey(r.departureTime) === hhmm, | ||||
| ); | ); | ||||
| const laneKeyList: string[] = Array.from(laneKeys); | const laneKeyList: string[] = Array.from(laneKeys); | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { FLOOR_A_KEY } from "@/config/demoFloors"; | |||||
| import type { RouteLaneImportPreviewRow } from "@/app/api/shop/client"; | import type { RouteLaneImportPreviewRow } from "@/app/api/shop/client"; | ||||
| import type { Truck } from "@/app/api/shop/actions"; | import type { Truck } from "@/app/api/shop/actions"; | ||||
| import { normalizeStoreId } from "@/app/utils/formatUtil"; | import { normalizeStoreId } from "@/app/utils/formatUtil"; | ||||
| @@ -118,7 +119,7 @@ export function mergeImportPreviewIntoLanes< | |||||
| String(previewRows[0].remark).trim() !== "" | String(previewRows[0].remark).trim() !== "" | ||||
| ? String(previewRows[0].remark).trim() | ? String(previewRows[0].remark).trim() | ||||
| : null; | : null; | ||||
| const storeId = normalizeStoreId(previewRows[0]?.storeId ?? "2F"); | |||||
| const storeId = normalizeStoreId(previewRows[0]?.storeId ?? FLOOR_A_KEY); | |||||
| const startTime = parseTimeForPreview(previewRows[0]?.departureTime ?? ""); | const startTime = parseTimeForPreview(previewRows[0]?.departureTime ?? ""); | ||||
| const existing = laneMap.get(laneId); | const existing = laneMap.get(laneId); | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -252,7 +253,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ | |||||
| const [approvedSortDir, setApprovedSortDir] = useState<"asc" | "desc">("asc"); | const [approvedSortDir, setApprovedSortDir] = useState<"asc" | "desc">("asc"); | ||||
| const [sectionDescriptionOptions, setSectionDescriptionOptions] = useState<string[]>([]); | const [sectionDescriptionOptions, setSectionDescriptionOptions] = useState<string[]>([]); | ||||
| const [stockTakeSectionOptions, setStockTakeSectionOptions] = useState<string[]>([]); | const [stockTakeSectionOptions, setStockTakeSectionOptions] = useState<string[]>([]); | ||||
| const [storeIdOptions, setStoreIdOptions] = useState<string[]>(["2F", "4F"]); | |||||
| const [storeIdOptions, setStoreIdOptions] = useState<string[]>([FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| const [searchSectionDescription, setSearchSectionDescription] = useState<string>("All"); | const [searchSectionDescription, setSearchSectionDescription] = useState<string>("All"); | ||||
| const [searchStockTakeSession, setSearchStockTakeSession] = useState<string>(""); | const [searchStockTakeSession, setSearchStockTakeSession] = useState<string>(""); | ||||
| const [searchItemKeyword, setSearchItemKeyword] = useState<string>(""); | const [searchItemKeyword, setSearchItemKeyword] = useState<string>(""); | ||||
| @@ -373,7 +374,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ | |||||
| .then((sections) => { | .then((sections) => { | ||||
| const descSet = new Set<string>(); | const descSet = new Set<string>(); | ||||
| const sectionSet = new Set<string>(); | const sectionSet = new Set<string>(); | ||||
| const storeSet = new Set<string>(["2F", "4F"]); | |||||
| const storeSet = new Set<string>([FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| sections.forEach((s) => { | sections.forEach((s) => { | ||||
| const section = s.stockTakeSection?.trim(); | const section = s.stockTakeSection?.trim(); | ||||
| if (section) sectionSet.add(section); | if (section) sectionSet.add(section); | ||||
| @@ -1375,7 +1376,7 @@ const ApproverStockTakeAll: React.FC<ApproverStockTakeAllProps> = ({ | |||||
| return Array.from(sectionSet).sort((a, b) => a.localeCompare(b)); | return Array.from(sectionSet).sort((a, b) => a.localeCompare(b)); | ||||
| }); | }); | ||||
| setStoreIdOptions((prev) => { | setStoreIdOptions((prev) => { | ||||
| const storeSet = new Set<string>([...prev, "2F", "4F"]); | |||||
| const storeSet = new Set<string>([...prev, FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| inventoryLotDetails.forEach((item) => { | inventoryLotDetails.forEach((item) => { | ||||
| const storeId = item.storeId?.trim(); | const storeId = item.storeId?.trim(); | ||||
| if (storeId) storeSet.add(storeId); | if (storeId) storeSet.add(storeId); | ||||
| @@ -1,5 +1,6 @@ | |||||
| "use client"; | "use client"; | ||||
| import { FLOOR_A_KEY, FLOOR_B_KEY } from "@/config/demoFloors"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -173,7 +174,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ | |||||
| const [marqueePreviewIds, setMarqueePreviewIds] = useState<Set<string>>(() => new Set()); | const [marqueePreviewIds, setMarqueePreviewIds] = useState<Set<string>>(() => new Set()); | ||||
| const [sectionDescriptionOptions, setSectionDescriptionOptions] = useState<string[]>([]); | const [sectionDescriptionOptions, setSectionDescriptionOptions] = useState<string[]>([]); | ||||
| const [stockTakeSectionOptions, setStockTakeSectionOptions] = useState<string[]>([]); | const [stockTakeSectionOptions, setStockTakeSectionOptions] = useState<string[]>([]); | ||||
| const [storeIdOptions, setStoreIdOptions] = useState<string[]>(["2F", "4F"]); | |||||
| const [storeIdOptions, setStoreIdOptions] = useState<string[]>([FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| const statusOptions = ["pending", "stockTaking", "approving", "completed"]; | const statusOptions = ["pending", "stockTaking", "approving", "completed"]; | ||||
| @@ -605,7 +606,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ | |||||
| setStockTakeSectionRows(Array.isArray(sections) ? sections : []); | setStockTakeSectionRows(Array.isArray(sections) ? sections : []); | ||||
| const descSet = new Set<string>(); | const descSet = new Set<string>(); | ||||
| const sectionSet = new Set<string>(); | const sectionSet = new Set<string>(); | ||||
| const storeIdSet = new Set<string>(["2F", "4F"]); | |||||
| const storeIdSet = new Set<string>([FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| sections.forEach((s) => { | sections.forEach((s) => { | ||||
| const section = s.stockTakeSection?.trim(); | const section = s.stockTakeSection?.trim(); | ||||
| if (section) sectionSet.add(section); | if (section) sectionSet.add(section); | ||||
| @@ -637,7 +638,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ | |||||
| }, [stockTakeSessions]); | }, [stockTakeSessions]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setStoreIdOptions((prev) => { | setStoreIdOptions((prev) => { | ||||
| const storeIdSet = new Set<string>([...prev, "2F", "4F"]); | |||||
| const storeIdSet = new Set<string>([...prev, FLOOR_A_KEY, FLOOR_B_KEY]); | |||||
| stockTakeSessions.forEach((item) => { | stockTakeSessions.forEach((item) => { | ||||
| const storeId = item.storeId?.trim(); | const storeId = item.storeId?.trim(); | ||||
| if (storeId) storeIdSet.add(storeId); | if (storeId) storeIdSet.add(storeId); | ||||
| @@ -0,0 +1,19 @@ | |||||
| /** Floor and bin labels stored in the anonymized demo database. */ | |||||
| export const FLOOR_A = "A/F"; | |||||
| export const FLOOR_A_KEY = "AF"; | |||||
| export const FLOOR_B = "B/F"; | |||||
| export const FLOOR_B_KEY = "BF"; | |||||
| export const FLOOR_C = "C/F"; | |||||
| export const FLOOR_C_KEY = "CF"; | |||||
| export const FLOOR_D = "D/F"; | |||||
| export const FLOOR_D_KEY = "DF"; | |||||
| export const LOC_HOLD = "LOC-HOLD"; | |||||
| export const LOC_WIP = "LOC-WIP"; | |||||
| export const LOC_PREFIX = "LOC-"; | |||||
| export const LABEL_A = "LABEL-A"; | |||||
| export const LABEL_B = "LABEL-B"; | |||||
| export const SETTING_SUPPLIERS_A = "DO.floor.suppliers.AF"; | |||||
| export const SETTING_SUPPLIERS_B = "DO.floor.suppliers.BF"; | |||||
| @@ -1,8 +1,9 @@ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | ||||
| export type FieldType = 'date' | 'text' | 'select' | 'number' | 'checkbox'; | |||||
| import { FLOOR_A, FLOOR_B } from "@/config/demoFloors"; | |||||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | import { NEXT_PUBLIC_API_URL } from "@/config/api"; | ||||
| export type FieldType = 'date' | 'text' | 'select' | 'number' | 'checkbox'; | |||||
| export interface ReportField { | export interface ReportField { | ||||
| label: string; | label: string; | ||||
| name: string; | name: string; | ||||
| @@ -62,13 +63,13 @@ const asyncItemCodeField = ( | |||||
| const floorOptionsAll: ReportField["options"] = [ | const floorOptionsAll: ReportField["options"] = [ | ||||
| { label: "全部", value: "All" }, | { label: "全部", value: "All" }, | ||||
| { label: "2/F", value: "2/F" }, | |||||
| { label: "4/F", value: "4/F" }, | |||||
| { label: FLOOR_A, value: FLOOR_A }, | |||||
| { label: FLOOR_B, value: FLOOR_B }, | |||||
| ]; | ]; | ||||
| const floorOptions: ReportField["options"] = [ | const floorOptions: ReportField["options"] = [ | ||||
| { label: "2/F", value: "2/F" }, | |||||
| { label: "4/F", value: "4/F" }, | |||||
| { label: FLOOR_A, value: FLOOR_A }, | |||||
| { label: FLOOR_B, value: FLOOR_B }, | |||||
| ]; | ]; | ||||
| const pasteItemCodeField = (): ReportField => ({ | const pasteItemCodeField = (): ReportField => ({ | ||||