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] =?UTF-8?q?=E6=88=90=E5=93=81=E5=87=BA=E5=80=89=E5=87=BA?= =?UTF-8?q?=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 && ( )}