From 087a940c40dba4a2e049e3562b15c49f91b6ccfb Mon Sep 17 00:00:00 2001 From: tommy Date: Mon, 31 Aug 2026 17:49:12 +0800 Subject: [PATCH] drink output qty --- src/app/(main)/production/page.tsx | 2 +- src/app/(main)/productionProcess/page.tsx | 2 +- src/app/api/jo/actions.ts | 32 ++ .../DrinkProductionQtyDashboard.tsx | 321 ++++++++++++++++-- src/i18n/en/productionProcess.json | 11 +- src/i18n/zh/productionProcess.json | 13 +- 6 files changed, 337 insertions(+), 44 deletions(-) diff --git a/src/app/(main)/production/page.tsx b/src/app/(main)/production/page.tsx index 57081100..91f3a2fe 100644 --- a/src/app/(main)/production/page.tsx +++ b/src/app/(main)/production/page.tsx @@ -38,7 +38,7 @@ const production: React.FC = async () => { {t("Create Process")} */} - + {/* Use new component */} diff --git a/src/app/(main)/productionProcess/page.tsx b/src/app/(main)/productionProcess/page.tsx index 94dbf7f9..6e7b793d 100644 --- a/src/app/(main)/productionProcess/page.tsx +++ b/src/app/(main)/productionProcess/page.tsx @@ -36,7 +36,7 @@ const productionProcess: React.FC = async () => { {t("Create Process")} */} - + }> diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 2815d43b..75efe315 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1741,6 +1741,38 @@ export const fetchDrinkProductionQty = cache( }, ); +export interface DrinkShipmentQtyDeliveryDetail { + deliveryOrderId: number; + deliveryOrderCode?: string | null; + deliveryDate?: string | null; + shopCode?: string | null; + shopName?: string | null; + deliveryOrderStatus?: string | null; + orderQty: number; + shippedQty: number; +} + +export interface DrinkShipmentQtyResponse { + itemCode?: string | null; + itemName?: string | null; + uom?: string | null; + totalOrderQty: number; + totalShippedQty: number; + deliveries?: DrinkShipmentQtyDeliveryDetail[]; +} + +export const fetchDrinkShipmentQty = cache(async (date?: string) => { + const params = new URLSearchParams(); + if (date) params.set("date", date); + const qs = params.toString(); + const url = `${BASE_API_URL}/product-process/Demo/DrinkShipmentQty${qs ? `?${qs}` : ""}`; + + return serverFetchJson(url, { + method: "GET", + next: { tags: ["drinkShipmentQty"] }, + }); +}); + // ===== Equipment Status Dashboard ===== export interface EquipmentStatusProcessInfo { diff --git a/src/components/ProductionProcess/DrinkProductionQtyDashboard.tsx b/src/components/ProductionProcess/DrinkProductionQtyDashboard.tsx index 2ae0d900..3a73b4c2 100644 --- a/src/components/ProductionProcess/DrinkProductionQtyDashboard.tsx +++ b/src/components/ProductionProcess/DrinkProductionQtyDashboard.tsx @@ -37,8 +37,11 @@ import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { fetchDrinkProductionQty, + fetchDrinkShipmentQty, DrinkProductionQtyResponse, DrinkProductionQtyJobOrderDetail, + DrinkShipmentQtyResponse, + DrinkShipmentQtyDeliveryDetail, } from "@/app/api/jo/actions"; import { arrayToDayjs } from "@/app/utils/formatUtil"; import { exportDrinkProductionQtyXlsx } from "@/components/ProductionProcess/exportDrinkProductionQtyXlsx"; @@ -55,7 +58,7 @@ const JO_STATUS_FILTER_VALUES = [ "completed", ] as const; -type DrinkViewMode = "actual" | "planned"; +type DrinkViewMode = "actual" | "planned" | "shipment"; const formatQty = (qty: number | null | undefined): string => { if (qty === null || qty === undefined || Number.isNaN(qty)) return "-"; @@ -100,10 +103,16 @@ const ProcessSummaryTimeText: React.FC<{ value: unknown }> = ({ value }) => { const getRowKey = (row: DrinkProductionQtyResponse, idx: number): string => `${row.itemCode || "unknown"}-${idx}`; +const getShipmentRowKey = (row: DrinkShipmentQtyResponse, idx: number): string => + `ship-${row.itemCode || "unknown"}-${idx}`; + /** FP-MTMS Version Checklist | Functions Ref. No. 26 | v1.0.0 | 2026-07-20 */ const DrinkProductionQtyDashboard: React.FC = () => { - const { t } = useTranslation(["common", "jo", "productionProcess"]); + const { t } = useTranslation(["common", "jo", "do", "productionProcess"]); const [data, setData] = useState([]); + const [shipmentData, setShipmentData] = useState( + [], + ); const [loading, setLoading] = useState(true); const [selectedDate, setSelectedDate] = useState(dayjs()); const [joStatusFilter, setJoStatusFilter] = useState(""); @@ -117,21 +126,62 @@ const DrinkProductionQtyDashboard: React.FC = () => { ); const isPlanned = viewMode === "planned"; + const isShipment = viewMode === "shipment"; + + const qtyHeaders = ((): { left: string; right: string } => { + switch (viewMode) { + case "shipment": + return { + left: t("Shipment Order Qty"), + right: t("Shipped Qty"), + }; + case "planned": + return { + left: t("Planned Output Qty"), + right: t("Actual Output Qty"), + }; + case "actual": + return { + left: t("Stock Req. Qty"), + right: t("Production Qty"), + }; + default: { + const _exhaustive: never = viewMode; + return _exhaustive; + } + } + })(); const loadData = useCallback(async () => { setLoading(true); + const dateStr = selectedDate.format("YYYY-MM-DD"); try { - const result = await fetchDrinkProductionQty( - selectedDate.format("YYYY-MM-DD"), - viewMode, - ); - setData(result || []); + switch (viewMode) { + case "shipment": { + const result = await fetchDrinkShipmentQty(dateStr); + setShipmentData(result || []); + setData([]); + break; + } + case "actual": + case "planned": { + const result = await fetchDrinkProductionQty(dateStr, viewMode); + setData(result || []); + setShipmentData([]); + break; + } + default: { + const _exhaustive: never = viewMode; + return _exhaustive; + } + } setExpandedRowKeys(new Set()); setLastDataRefreshTime(dayjs()); refreshCountRef.current += 1; } catch (error) { console.error("Error fetching drink production qty:", error); setData([]); + setShipmentData([]); setExpandedRowKeys(new Set()); } finally { setLoading(false); @@ -190,6 +240,33 @@ const DrinkProductionQtyDashboard: React.FC = () => { ); }; + const renderDoStatusChip = (status: string | null | undefined) => { + if (!status) return <>—; + const normalized = status.toLowerCase(); + let label: string; + switch (normalized) { + case "pending": + label = t("Drink do status pending"); + break; + case "receiving": + label = t("Drink do status receiving"); + break; + case "completed": + label = t("Drink do status completed"); + break; + default: + label = t(status, { ns: "do", defaultValue: status }); + break; + } + return ( + + ); + }; + return ( @@ -216,29 +293,31 @@ const DrinkProductionQtyDashboard: React.FC = () => { /> - - - {t("Job Order Status")} - - { + setJoStatusFilter(String(e.target.value)); + }} + > + + {t("All")} - ))} - - + {JO_STATUS_FILTER_VALUES.map((v) => ( + + {t(v, { ns: "jo" })} + + ))} + + + )} @@ -246,7 +325,9 @@ const DrinkProductionQtyDashboard: React.FC = () => { variant="outlined" size="small" startIcon={} - disabled={loading || data.length === 0} + disabled={ + loading || (isShipment ? shipmentData.length === 0 : data.length === 0) + } sx={{ display: "none" }} onClick={() => { exportDrinkProductionQtyXlsx({ @@ -298,6 +379,9 @@ const DrinkProductionQtyDashboard: React.FC = () => { {t("Drink detail mode: planned")} + + {t("Drink detail mode: shipment")} + @@ -347,22 +431,185 @@ const DrinkProductionQtyDashboard: React.FC = () => { - {isPlanned - ? t("Planned Output Qty") - : t("Stock Req. Qty")} + {qtyHeaders.left} - {isPlanned - ? t("Actual Output Qty") - : t("Production Qty")} + {qtyHeaders.right} - {data.length === 0 ? ( + {isShipment ? ( + shipmentData.length === 0 ? ( + + + + {t("No data available")} + + + + ) : ( + shipmentData.map((row, idx) => { + const rowKey = getShipmentRowKey(row, idx); + const deliveries: DrinkShipmentQtyDeliveryDetail[] = + row.deliveries ?? []; + const isExpanded = expandedRowKeys.has(rowKey); + const hasExpandable = deliveries.length > 0; + + return ( + + + + {hasExpandable ? ( + toggleRowExpanded(rowKey)} + > + {isExpanded ? ( + + ) : ( + + )} + + ) : null} + + + + {row.itemCode || "-"} + + + + + {row.itemName || "-"} + + + + + {row.uom || "-"} + + + + + {formatQty(row.totalOrderQty)} + + + + + {formatQty(row.totalShippedQty)} + + + + {hasExpandable && ( + + + + + + + + + {t("Delivery Order Code", { + ns: "do", + })} + + + {t("Delivery Order Status", { + ns: "do", + })} + + + {t("Shop Name", { ns: "do" })} + + + {t("Delivery Date", { ns: "do" })} + + + {t("Shipment Order Qty")} + + + {t("Shipped Qty")} + + + + + {deliveries.map((delivery) => ( + + + {delivery.deliveryOrderId > 0 ? ( + + {delivery.deliveryOrderCode || + `DO-${delivery.deliveryOrderId}`} + + ) : ( + delivery.deliveryOrderCode || + "-" + )} + + + {renderDoStatusChip( + delivery.deliveryOrderStatus, + )} + + + {delivery.shopName || + delivery.shopCode || + "-"} + + + {formatProductionDate( + delivery.deliveryDate, + )} + + + {formatQty(delivery.orderQty)} + + + {formatQty(delivery.shippedQty)} + + + ))} + +
+
+
+
+
+ )} +
+ ); + }) + ) + ) : data.length === 0 ? (