From 3c1b180148b2cbf90441d94381b3398b66a0e731 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 28 Apr 2026 19:21:19 +0800 Subject: [PATCH] update jo search --- src/app/api/doworkbench/actions.ts | 4 +- src/app/api/jo/actions.ts | 21 +- .../GoodPickExecutionWorkbenchRecord.tsx | 3 +- .../JoWorkbench/JoPickOrderList.tsx | 198 ++++++++++-------- src/i18n/zh/common.json | 7 + src/i18n/zh/jo.json | 7 +- 6 files changed, 147 insertions(+), 93 deletions(-) diff --git a/src/app/api/doworkbench/actions.ts b/src/app/api/doworkbench/actions.ts index 3f1c97f..4c04edc 100644 --- a/src/app/api/doworkbench/actions.ts +++ b/src/app/api/doworkbench/actions.ts @@ -365,8 +365,8 @@ export async function printWorkbenchLotLabel(params: { searchParams.set("inventoryLotLineId", String(params.inventoryLotLineId)); searchParams.set("printerId", String(params.printerId)); searchParams.set("printQty", String(params.printQty)); - return serverFetchJson( - `${BASE_API_URL}/inventoryLotLine/print-label?${searchParams.toString()}`, + return serverFetchJson( + `${BASE_API_URL}/inventoryLotLine/workbench/print-label?${searchParams.toString()}`, { method: "GET", cache: "no-store" }, ); } \ No newline at end of file diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index efa21a2..e756576 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -536,7 +536,9 @@ export interface AllJoPickOrderResponse { jobOrderType: string | null; itemId: number; itemName: string; + bomDescription?: string | null; lotNo: string | null; + planStart?: string | number[] | null; reqQty: number; uomId: number; uomName: string; @@ -733,10 +735,27 @@ export const fetchJobOrderLotsHierarchicalByPickOrderIdWorkbench = cache( // NOTE: Do NOT wrap in `cache()` because the list needs to reflect just-completed lines // immediately when navigating back from JobPickExecution. -export const fetchAllJoPickOrders = async (type?: string | null, floor?: string | null) => { +export interface FetchAllJoPickOrdersFilters { + jobOrderCode?: string | null; + pickOrderCode?: string | null; + itemName?: string | null; + bomDescription?: string | null; + planStart?: string | null; +} + +export const fetchAllJoPickOrders = async ( + type?: string | null, + floor?: string | null, + filters?: FetchAllJoPickOrdersFilters, +) => { const params = new URLSearchParams(); if (type) params.set("type", type); if (floor) params.set("floor", floor); + if (filters?.jobOrderCode) params.set("jobOrderCode", filters.jobOrderCode); + if (filters?.pickOrderCode) params.set("pickOrderCode", filters.pickOrderCode); + if (filters?.itemName) params.set("itemName", filters.itemName); + if (filters?.bomDescription) params.set("bomDescription", filters.bomDescription); + if (filters?.planStart) params.set("planStart", filters.planStart); const query = params.toString() ? `?${params.toString()}` : ""; return serverFetchJson( `${BASE_API_URL}/jo/AllJoPickOrder${query}`, diff --git a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx index 5554ae7..7c2e9fd 100644 --- a/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx +++ b/src/components/DoWorkbench/GoodPickExecutionWorkbenchRecord.tsx @@ -398,6 +398,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({ hierarchicalData.pickOrders.forEach((po: any) => { po.pickOrderLines?.forEach((line: any) => { + const lineRequiredQty = Number(line.requiredQty ?? line.qty ?? 0); const lineStockouts = line.stockouts || []; const lots = line.lots || []; @@ -413,7 +414,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC = ({ lotNo: so.lotNo || lot.lotNo, location: so.location || lot.location, deliveryOrderCode: po.deliveryOrderCodes?.[0] || po.deliveryOrderCode, - requiredQty: lot.requiredQty, + requiredQty: lineRequiredQty, actualPickQty: so.qty ?? lot.actualPickQty ?? 0, processingStatus: toProc(so.status), stockOutLineStatus: so.status, diff --git a/src/components/JoWorkbench/JoPickOrderList.tsx b/src/components/JoWorkbench/JoPickOrderList.tsx index adacd1f..01803ba 100644 --- a/src/components/JoWorkbench/JoPickOrderList.tsx +++ b/src/components/JoWorkbench/JoPickOrderList.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Box, Button, @@ -16,6 +16,8 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useTranslation } from "react-i18next"; import { fetchAllJoPickOrders, AllJoPickOrderResponse } from "@/app/api/jo/actions"; import JobPickExecution from "./newJobPickExecution"; +import SearchBox, { Criterion } from "../SearchBox"; +import dayjs from "dayjs"; interface Props { /** Reserved for tabs parity with Jodetail; not used in workbench list yet. */ @@ -25,20 +27,88 @@ interface Props { /** Jo workbench: same list + detail flow as Jodetail `JoPickOrderList`, detail uses `JoWorkbench/newJobPickExecution`. */ const JoPickOrderList: React.FC = () => { const { t } = useTranslation(["common", "jo"]); + const today = dayjs().format("YYYY-MM-DD"); const [loading, setLoading] = useState(false); const [pickOrders, setPickOrders] = useState([]); const [selectedPickOrderId, setSelectedPickOrderId] = useState(undefined); const [selectedJobOrderId, setSelectedJobOrderId] = useState(undefined); - type PickOrderFilter = "all" | "drink" | "Powder_Mixture" | "other"; - const [filter, setFilter] = useState("all"); + const [searchQuery, setSearchQuery] = useState>({ planStart: today }); + type FloorFilter = "ALL" | "2F" | "3F" | "4F" | "NO_LOT"; - const [floorFilter, setFloorFilter] = useState("ALL"); - const fetchPickOrders = useCallback(async () => { + + const searchCriteria: Criterion[] = [ + { label: t("Job Order Code"), paramName: "jobOrderCode", type: "text" }, + { label: t("Pick Order"), paramName: "pickOrderCode", type: "text" }, + { label: t("Item Name"), paramName: "itemName", type: "text" }, + { + label: t("Job Order Type"), + paramName: "BOM Description", + type: "select-labelled", + options: [ + { label: t("All"), value: "All" }, + { label: t("FG"), value: "FG" }, + { label: t("WIP"), value: "WIP" }, + ], + }, + { label: t("Plan Start"), paramName: "planStart", type: "date" }, + { + label: t("BOM Type"), + paramName: "bomType", + type: "select-labelled", + options: [ + { label: t("All"), value: "All" }, + { label: t("Drink"), value: "drink" }, + { label: t("Powder Mixture"), value: "Powder_Mixture" }, + { label: t("Other"), value: "other" }, + ], + }, + { + label: t("Floor"), + paramName: "floor", + type: "select-labelled", + options: [ + { label: t("All"), value: "ALL" }, + { label: "2F", value: "2F" }, + { label: "3F", value: "3F" }, + { label: "4F", value: "4F" }, + { label: t("No Lot"), value: "NO_LOT" }, + ], + }, + ]; + + const selectedFloor: FloorFilter = (() => { + const floor = String(searchQuery.floor || "ALL"); + if (floor === "2F" || floor === "3F" || floor === "4F" || floor === "NO_LOT") { + return floor; + } + return "ALL"; + })(); + + const fetchPickOrders = useCallback(async (query?: Record) => { setLoading(true); try { - const typeParam = filter === "all" ? undefined : filter; - const floorParam = floorFilter === "ALL" ? undefined : floorFilter; - const data = await fetchAllJoPickOrders(typeParam, floorParam); + const currentQuery = query ?? { planStart: today }; + const bomTypeValue = String(currentQuery.bomType || "All"); + const floorValue = String(currentQuery.floor || "ALL"); + const isAllOption = (value: string) => { + const normalized = value.trim().toLowerCase(); + return normalized === "" || normalized === "all"; + }; + const typeParam = isAllOption(bomTypeValue) ? undefined : bomTypeValue; + const floorParam = isAllOption(floorValue) ? undefined : floorValue; + const jobOrderCode = String(currentQuery.jobOrderCode || "").trim(); + const pickOrderCode = String(currentQuery.pickOrderCode || "").trim(); + const itemName = String(currentQuery.itemName || "").trim(); + const bomDescription = String(currentQuery.bomDescription || "").trim(); + const planStart = String(currentQuery.planStart || "").trim(); + + const data = await fetchAllJoPickOrders(typeParam, floorParam, { + jobOrderCode: jobOrderCode || undefined, + pickOrderCode: pickOrderCode || undefined, + itemName: itemName || undefined, + bomDescription: isAllOption(bomDescription) ? undefined : bomDescription, + planStart: planStart || undefined, + }); setPickOrders(Array.isArray(data) ? data : []); } catch (e) { console.error(e); @@ -46,16 +116,28 @@ const JoPickOrderList: React.FC = () => { } finally { setLoading(false); } - }, [filter, floorFilter]); + }, [today]); + + const handleSearch = useCallback((query: Record) => { + const nextQuery = { ...query }; + setSearchQuery(nextQuery); + void fetchPickOrders(nextQuery); + }, [fetchPickOrders]); + + const handleSearchReset = useCallback(() => { + const resetQuery = {}; + setSearchQuery(resetQuery); + void fetchPickOrders(resetQuery); + }, [fetchPickOrders]); useEffect(() => { - fetchPickOrders(); - }, [fetchPickOrders, filter, floorFilter]); + void fetchPickOrders({ planStart: today }); + }, [fetchPickOrders, today]); const handleBackToList = useCallback(() => { setSelectedPickOrderId(undefined); setSelectedJobOrderId(undefined); - fetchPickOrders(); - }, [fetchPickOrders]); + void fetchPickOrders(searchQuery); + }, [fetchPickOrders, searchQuery]); if (selectedPickOrderId !== undefined) { return ( @@ -78,84 +160,23 @@ const JoPickOrderList: React.FC = () => { return ( + + + {loading ? ( ) : ( - - - - - - - - - - - - - - {t("Total pick orders")}: {pickOrders.length} - + {pickOrders.map((pickOrder) => { const status = String(pickOrder.jobOrderStatus || ""); @@ -166,9 +187,9 @@ const JoPickOrderList: React.FC = () => { : statusLower === "pending" || statusLower === "processing" ? "primary" : "default"; - + const finishedCount = pickOrder.finishedPickOLineCount ?? 0; - + return ( = () => { {t("Item Name")}: {pickOrder.itemName} + {pickOrder.bomDescription ? ` (${t(pickOrder.bomDescription)})` : ""} {t("Required Qty")}: {pickOrder.reqQty} ({pickOrder.uomName}) - {floorFilter === "ALL" ? ( + {selectedFloor === "ALL" ? ( <> {pickOrder.floorPickCounts?.map(({ floor, finishedCount, totalCount }) => ( = () => { )} - ) : floorFilter === "NO_LOT" ? ( + ) : selectedFloor === "NO_LOT" ? ( !!pickOrder.noLotPickCount && ( = () => { ) ) : ( pickOrder.floorPickCounts - ?.filter((c) => c.floor === floorFilter) + ?.filter((c) => c.floor === selectedFloor) .map(({ floor, finishedCount, totalCount }) => ( = () => { )} - +