From 9737d94e49b0f41f3be76334fa36c4bc45ce8ea2 Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Sat, 4 Apr 2026 00:08:01 +0800 Subject: [PATCH] Truck Routing Summary List --- .../(main)/report/truckRoutingSummaryApi.ts | 52 +++++++ .../FinishedGoodSearch/FinishedGoodSearch.tsx | 3 + .../TruckRoutingSummaryTab.tsx | 128 ++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/app/(main)/report/truckRoutingSummaryApi.ts create mode 100644 src/components/FinishedGoodSearch/TruckRoutingSummaryTab.tsx diff --git a/src/app/(main)/report/truckRoutingSummaryApi.ts b/src/app/(main)/report/truckRoutingSummaryApi.ts new file mode 100644 index 0000000..c553015 --- /dev/null +++ b/src/app/(main)/report/truckRoutingSummaryApi.ts @@ -0,0 +1,52 @@ +"use client"; + +import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; +import { NEXT_PUBLIC_API_URL } from "@/config/api"; + +export interface ReportOption { + label: string; + value: string; +} + +export async function fetchTruckRoutingStoreOptions(): Promise { + const response = await clientAuthFetch( + `${NEXT_PUBLIC_API_URL}/truck-routing-summary/store-options`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch store options: ${response.status}`); + } + + const data = await response.json(); + if (!Array.isArray(data)) return []; + return data.map((item: any) => ({ + label: item?.label ?? item?.value ?? "", + value: item?.value ?? "", + })); +} + +export async function fetchTruckRoutingLaneOptions(storeId?: string): Promise { + const qs = storeId ? `?storeId=${encodeURIComponent(storeId)}` : ""; + const response = await clientAuthFetch( + `${NEXT_PUBLIC_API_URL}/truck-routing-summary/lane-options${qs}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch lane options: ${response.status}`); + } + + const data = await response.json(); + if (!Array.isArray(data)) return []; + return data.map((item: any) => ({ + label: item?.label ?? item?.value ?? "", + value: item?.value ?? "", + })); +} diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 8ae9faa..bff5df8 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -41,6 +41,7 @@ import dayjs, { Dayjs } from 'dayjs'; import { PrinterCombo } from "@/app/api/settings/printer"; import { Autocomplete } from "@mui/material"; import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable"; +import TruckRoutingSummaryTab from "./TruckRoutingSummaryTab"; interface Props { pickOrders: PickOrderResult[]; @@ -710,6 +711,7 @@ const handleAssignByLane = useCallback(async ( + @@ -764,6 +766,7 @@ const handleAssignByLane = useCallback(async ( listScope="all" /> )} + {tabIndex === 5 && } ); diff --git a/src/components/FinishedGoodSearch/TruckRoutingSummaryTab.tsx b/src/components/FinishedGoodSearch/TruckRoutingSummaryTab.tsx new file mode 100644 index 0000000..832c1f7 --- /dev/null +++ b/src/components/FinishedGoodSearch/TruckRoutingSummaryTab.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; +import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; +import { NEXT_PUBLIC_API_URL } from "@/config/api"; +import { fetchTruckRoutingLaneOptions, fetchTruckRoutingStoreOptions, ReportOption } from "@/app/(main)/report/truckRoutingSummaryApi"; + +const TruckRoutingSummaryTab: React.FC = () => { + const [storeOptions, setStoreOptions] = useState([]); + const [laneOptions, setLaneOptions] = useState([]); + const [storeId, setStoreId] = useState(""); + const [truckLanceCode, setTruckLanceCode] = useState(""); + const [date, setDate] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + fetchTruckRoutingStoreOptions() + .then(setStoreOptions) + .catch((err) => console.error("Failed to load store options", err)); + }, []); + + const onStoreChange = async (value: string) => { + setStoreId(value); + setTruckLanceCode(""); + try { + const lanes = await fetchTruckRoutingLaneOptions(value); + setLaneOptions(lanes); + } catch (error) { + console.error("Failed to load lane options", error); + setLaneOptions([]); + } + }; + + const canDownload = storeId && truckLanceCode && date && !loading; + + const onDownload = async () => { + if (!canDownload) return; + setLoading(true); + try { + const qs = new URLSearchParams({ + storeId, + truckLanceCode, + date, + }).toString(); + const url = `${NEXT_PUBLIC_API_URL}/truck-routing-summary/print?${qs}`; + const response = await clientAuthFetch(url, { + method: "GET", + headers: { Accept: "application/pdf" }, + }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + + const blob = await response.blob(); + const downloadUrl = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = downloadUrl; + link.setAttribute("download", "TruckRoutingSummary.pdf"); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(downloadUrl); + } catch (error) { + console.error("Failed to download Truck Routing Summary", error); + alert("下載送貨路線摘要失敗,請稍後再試。"); + } finally { + setLoading(false); + } + }; + + return ( + + + 送貨路線摘要 + + + onStoreChange(e.target.value)} + > + {storeOptions.map((opt) => ( + + {opt.label} + + ))} + + setTruckLanceCode(e.target.value)} + disabled={!storeId} + > + {laneOptions.map((opt) => ( + + {opt.label} + + ))} + + setDate(e.target.value)} + /> + + + + ); +}; + +export default TruckRoutingSummaryTab;