From 510d3fd831862f5f44597cb032d6d91cfe456576 Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Wed, 22 Apr 2026 17:03:53 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=88=90=E5=93=81=E5=87=BA=E5=80=89?= =?UTF-8?q?=E5=87=BA=E7=AE=B1=E6=95=B8=E9=87=8F=20Update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FinishedGoodCartonDashboardTab.tsx | 255 ++++++++++++++++++ .../FinishedGoodSearch/FinishedGoodSearch.tsx | 9 +- 2 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx diff --git a/src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx b/src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx new file mode 100644 index 0000000..ce97906 --- /dev/null +++ b/src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx @@ -0,0 +1,255 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + Alert, + Box, + CircularProgress, + Grid, + MenuItem, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, +} from "@mui/material"; +import type { ApexOptions } from "apexcharts"; +import dayjs from "dayjs"; +import { + CompletedDoPickOrderResponse, + fetchCompletedDoPickOrdersAll, +} from "@/app/api/pickOrder/actions"; +import SafeApexCharts from "@/components/charts/SafeApexCharts"; + +type FloorFilter = "all" | "2/F" | "4/F"; + +type DailySummaryRow = { + date: string; + floor2F: number; + floor4F: number; + truckX: number; + total: number; +}; + +const FinishedGoodCartonDashboardTab: React.FC = () => { + const [floor, setFloor] = useState("all"); + const [date, setDate] = useState(dayjs().format("YYYY-MM-DD")); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [records, setRecords] = useState([]); + + const loadData = useCallback(async () => { + setLoading(true); + setError(""); + try { + const data = await fetchCompletedDoPickOrdersAll( + date ? { targetDate: date } : undefined, + ); + setRecords(data); + } catch (err) { + console.error("Failed to load finished good carton dashboard data", err); + setError("載入成品出倉出箱數量失敗,請稍後再試。"); + setRecords([]); + } finally { + setLoading(false); + } + }, [date]); + + useEffect(() => { + loadData(); + }, [loadData]); + + const rows = useMemo(() => { + const filtered = + floor === "all" ? records : records.filter((record) => record.storeId === floor); + + const summary = new Map(); + + filtered.forEach((record) => { + const day = dayjs(record.deliveryDate).isValid() + ? dayjs(record.deliveryDate).format("YYYY-MM-DD") + : "-"; + const cartonQty = Number(record.numberOfCartons ?? 0); + + const current = summary.get(day) ?? { + date: day, + floor2F: 0, + floor4F: 0, + truckX: 0, + total: 0, + }; + + if (record.storeId === "2/F") { + current.floor2F += cartonQty; + } + if (record.storeId === "4/F") { + current.floor4F += cartonQty; + } + if (String(record.truckLanceCode ?? "").trim() === "車線-X") { + current.truckX += cartonQty; + } + + current.total += cartonQty; + summary.set(day, current); + }); + + return Array.from(summary.values()).sort((a, b) => b.date.localeCompare(a.date)); + }, [records, floor]); + + const chartOptions = useMemo( + () => ({ + chart: { + type: "bar", + toolbar: { show: false }, + }, + colors: ["#1976d2", "#9c27b0", "#ff9800", "#2e7d32"], + dataLabels: { enabled: false }, + stroke: { show: true, width: 1, colors: ["transparent"] }, + plotOptions: { + bar: { + horizontal: false, + borderRadius: 3, + columnWidth: "55%", + }, + }, + xaxis: { + categories: rows.map((row) => row.date), + title: { text: "日期" }, + }, + yaxis: { + title: { text: "箱數" }, + labels: { + formatter: (val) => Number(val || 0).toLocaleString("zh-HK"), + }, + }, + tooltip: { + y: { + formatter: (val) => `${Number(val || 0).toLocaleString("zh-HK")} 箱`, + }, + }, + legend: { + position: "top", + }, + noData: { + text: "沒有圖表資料", + }, + }), + [rows], + ); + + const chartSeries = useMemo( + () => [ + { name: "2/F", data: rows.map((row) => row.floor2F) }, + { name: "4/F", data: rows.map((row) => row.floor4F) }, + { name: "車線-X", data: rows.map((row) => row.truckX) }, + { name: "總數", data: rows.map((row) => row.total) }, + ], + [rows], + ); + + const summary = useMemo(() => { + return rows.reduce( + (acc, row) => { + acc.floor2F += row.floor2F; + acc.floor4F += row.floor4F; + acc.truckX += row.truckX; + acc.total += row.total; + return acc; + }, + { floor2F: 0, floor4F: 0, truckX: 0, total: 0 }, + ); + }, [rows]); + + return ( + + + 成品出倉出箱數量 + + + {error && ( + + {error} + + )} + + {loading ? ( + + + + ) : ( + + + + setFloor(event.target.value as FloorFilter)} + > + 全部 + 2/F + 4/F + + + + setDate(event.target.value)} + /> + + + + + + + + + + 2/F 出箱數 + {summary.floor2F.toLocaleString("zh-HK")} + + + 4/F 出箱數 + {summary.floor4F.toLocaleString("zh-HK")} + + + 車線-X 出箱數 + {summary.truckX.toLocaleString("zh-HK")} + + + 總出箱數 + {summary.total.toLocaleString("zh-HK")} + + +
+
+
+ + + + + +
+
+ )} +
+ ); +}; + +export default FinishedGoodCartonDashboardTab; diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 6c1e291..f2c4292 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -42,6 +42,7 @@ import { PrinterCombo } from "@/app/api/settings/printer"; import { Autocomplete } from "@mui/material"; import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; import TruckRoutingSummaryTab, { TruckRoutingSummaryFilters } from "./TruckRoutingSummaryTab"; +import FinishedGoodCartonDashboardTab from "./FinishedGoodCartonDashboardTab"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { fetchTruckRoutingSummaryPrecheck } from "@/app/(main)/report/truckRoutingSummaryApi"; @@ -378,7 +379,7 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState { - if (tabIndex === 5) { + if (tabIndex === 6) { logFeatureUsage(FEATURE_USAGE.TRUCK_ROUTING_SUMMARY, FEATURE_USAGE_ACTION.PAGE_VIEW); } }, [tabIndex]); @@ -831,6 +832,7 @@ const handleAssignByLane = useCallback(async ( + @@ -887,6 +889,9 @@ const handleAssignByLane = useCallback(async ( /> )} {tabIndex === 5 && ( + + )} + {tabIndex === 6 && ( )} From b9a9deb1d44ae113c10eb3690feb34910d65d4f8 Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Wed, 22 Apr 2026 19:02:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=B7=A5=E5=96=AE=E6=9D=BF=E9=A0=AD?= =?UTF-8?q?=E7=B4=99=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/jo/actions.ts | 4 ++ .../Jodetail/completeJobOrderRecord.tsx | 37 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 5890d78..7b0dc2b 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -133,6 +133,7 @@ export interface PrintPickRecordRequest{ pickOrderId: number; printerId: number; printQty: number; + floor?: "2F" | "3F" | "4F" | "ALL"; } export interface PrintPickRecordResponse{ @@ -1318,6 +1319,9 @@ export async function PrintPickRecord(request: PrintPickRecordRequest){ if (request.printQty !== null && request.printQty !== undefined) { params.append('printQty', request.printQty.toString()); } + if (request.floor) { + params.append('floor', request.floor); + } //const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{ const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{ diff --git a/src/components/Jodetail/completeJobOrderRecord.tsx b/src/components/Jodetail/completeJobOrderRecord.tsx index 74aec53..31f521d 100644 --- a/src/components/Jodetail/completeJobOrderRecord.tsx +++ b/src/components/Jodetail/completeJobOrderRecord.tsx @@ -378,7 +378,10 @@ const CompleteJobOrderRecord: React.FC = ({ })); }, []); - const handlePickRecord = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => { + const handlePickRecord = useCallback(async ( + jobOrderPickOrder: CompletedJobOrderPickOrder, + floor: "2F" | "3F" | "4F" | "ALL" + ) => { try { if (!jobOrderPickOrder) { console.error("No selected job order pick order available"); @@ -418,7 +421,8 @@ const CompleteJobOrderRecord: React.FC = ({ const printRequest = { pickOrderId: pickOrderId, printerId: printerId, - printQty: printQty + printQty: printQty, + floor, }; console.log("Printing Pick Record with request: ", printRequest); @@ -703,12 +707,33 @@ const CompleteJobOrderRecord: React.FC = ({ > {t("View Details")} - + + + From 05feee9c762d1998150c68794874d05cfd8022a6 Mon Sep 17 00:00:00 2001 From: "vluk@2fi-solutions.com.hk" Date: Thu, 23 Apr 2026 10:38:34 +0800 Subject: [PATCH 3/3] no message --- src/app/(main)/testing/page.tsx | 6 +++--- src/app/api/laserPrint/actions.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx index 14f4855..9ef0e81 100644 --- a/src/app/(main)/testing/page.tsx +++ b/src/app/(main)/testing/page.tsx @@ -222,7 +222,7 @@ export default function TestingPage() { const lim = parseInt(laserAutoLimit.trim(), 10); const report = await runLaserBag2AutoSend({ planStart: laserAutoPlanDate, - limitPerRun: Number.isFinite(lim) ? lim : 0, + limitPerRun: Number.isFinite(lim) ? lim : 1, }); setLaserAutoReport(report); try { @@ -531,11 +531,11 @@ export default function TestingPage() { /> setLaserAutoLimit(e.target.value)} sx={{ width: 200 }} - helperText="手動測試建議 1;排程預設每分鐘最多 1 筆" + helperText="目前後端會限制為第一筆;此欄位保留給未來調整" />