From d09ee3a962e9cf76d7cae4f74d0df612f0bf664d Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Mon, 19 Jan 2026 12:23:41 +0800 Subject: [PATCH] Update --- src/app/(main)/settings/printer/page.tsx | 47 +++++ src/app/api/settings/printer/actions.ts | 53 +++++ src/app/api/settings/printer/index.ts | 21 +- src/app/api/warehouse/index.ts | 2 +- src/app/utils/fetchUtil.ts | 4 +- .../NavigationContent/NavigationContent.tsx | 5 + .../PrinterSearch/PrinterSearch.tsx | 182 ++++++++++++++++++ .../PrinterSearch/PrinterSearchLoading.tsx | 39 ++++ .../PrinterSearch/PrinterSearchWrapper.tsx | 25 +++ src/components/PrinterSearch/index.ts | 2 + .../qrCodeHandleWarehouseSearch.tsx | 4 + .../qrCodeHandleWarehouseSearchWrapper.tsx | 25 ++- src/i18n/zh/common.json | 3 +- 13 files changed, 398 insertions(+), 14 deletions(-) create mode 100644 src/app/(main)/settings/printer/page.tsx create mode 100644 src/app/api/settings/printer/actions.ts create mode 100644 src/components/PrinterSearch/PrinterSearch.tsx create mode 100644 src/components/PrinterSearch/PrinterSearchLoading.tsx create mode 100644 src/components/PrinterSearch/PrinterSearchWrapper.tsx create mode 100644 src/components/PrinterSearch/index.ts diff --git a/src/app/(main)/settings/printer/page.tsx b/src/app/(main)/settings/printer/page.tsx new file mode 100644 index 0000000..ee662a2 --- /dev/null +++ b/src/app/(main)/settings/printer/page.tsx @@ -0,0 +1,47 @@ +import { Metadata } from "next"; +import { getServerI18n, I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import { Suspense } from "react"; +import { Stack } from "@mui/material"; +import { Button } from "@mui/material"; +import Link from "next/link"; +import PrinterSearch from "@/components/PrinterSearch"; +import Add from "@mui/icons-material/Add"; + +export const metadata: Metadata = { + title: "Printer Management", +}; + +const Printer: React.FC = async () => { + const { t } = await getServerI18n("common"); + + return ( + <> + + + {t("Printer")} + + + + + }> + + + + + ); +}; + +export default Printer; diff --git a/src/app/api/settings/printer/actions.ts b/src/app/api/settings/printer/actions.ts new file mode 100644 index 0000000..9f6b64b --- /dev/null +++ b/src/app/api/settings/printer/actions.ts @@ -0,0 +1,53 @@ +"use server"; + +import { + serverFetchJson, + serverFetchWithNoContent, +} from "../../../utils/fetchUtil"; +import { BASE_API_URL } from "../../../../config/api"; +import { revalidateTag } from "next/cache"; +import { PrinterResult } from "."; + +export interface PrinterInputs { + name?: string; + code?: string; + type?: string; + description?: string; + ip?: string; + port?: number; +} + +export const fetchPrinterDetails = async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/printers/${id}`, { + next: { tags: ["printers"] }, + }); +}; + +export const editPrinter = async (id: number, data: PrinterInputs) => { + const result = await serverFetchWithNoContent(`${BASE_API_URL}/printers/${id}`, { + method: "PUT", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); + revalidateTag("printers"); + return result; +}; + +export const createPrinter = async (data: PrinterInputs) => { + const result = await serverFetchWithNoContent(`${BASE_API_URL}/printers`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); + revalidateTag("printers"); + return result; +}; + +export const deletePrinter = async (id: number) => { + const result = await serverFetchWithNoContent(`${BASE_API_URL}/printers/${id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }); + revalidateTag("printers"); + return result; +}; diff --git a/src/app/api/settings/printer/index.ts b/src/app/api/settings/printer/index.ts index d41dc48..20bd6c7 100644 --- a/src/app/api/settings/printer/index.ts +++ b/src/app/api/settings/printer/index.ts @@ -15,8 +15,25 @@ export interface PrinterCombo { port?: number; } +export interface PrinterResult { + action: any; + id: number; + name?: string; + code?: string; + type?: string; + description?: string; + ip?: string; + port?: number; +} + export const fetchPrinterCombo = cache(async () => { return serverFetchJson(`${BASE_API_URL}/printers/combo`, { - next: { tags: ["qcItems"] }, + next: { tags: ["printers"] }, }) -}) \ No newline at end of file +}) + +export const fetchPrinters = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/printers`, { + next: { tags: ["printers"] }, + }); +}); \ No newline at end of file diff --git a/src/app/api/warehouse/index.ts b/src/app/api/warehouse/index.ts index 871693b..dff7588 100644 --- a/src/app/api/warehouse/index.ts +++ b/src/app/api/warehouse/index.ts @@ -13,7 +13,7 @@ export interface WarehouseResult { warehouse?: string; area?: string; slot?: string; - order?: number; + order?: string; stockTakeSection?: string; } diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index 571fdab..357dbdf 100644 --- a/src/app/utils/fetchUtil.ts +++ b/src/app/utils/fetchUtil.ts @@ -35,7 +35,7 @@ export async function serverFetchWithNoContent(...args: FetchParams) { const response = await serverFetch(...args); if (response.ok) { - return response.status; // 204 No Content, e.g. for delete data + return response.status; } else { switch (response.status) { case 401: @@ -52,7 +52,6 @@ export async function serverFetchWithNoContent(...args: FetchParams) { } export const serverFetch: typeof fetch = async (input, init) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const session = await getServerSession(authOptions); const accessToken = session?.accessToken; @@ -129,7 +128,6 @@ export async function serverFetchBlob(...args: FetchPara while (!done) { const read = await reader?.read(); - // version 1 if (read?.done) { done = true; } else { diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 4fe6c12..3b43e8a 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -362,6 +362,11 @@ const NavigationContent: React.FC = () => { label: "QC Item All", path: "/settings/qcItemAll", }, + { + icon: , + label: "QR Code Handle", + path: "/settings/qrCodeHandle", + }, // { // icon: , // label: "Mail", diff --git a/src/components/PrinterSearch/PrinterSearch.tsx b/src/components/PrinterSearch/PrinterSearch.tsx new file mode 100644 index 0000000..ff0fe1c --- /dev/null +++ b/src/components/PrinterSearch/PrinterSearch.tsx @@ -0,0 +1,182 @@ +"use client"; + +import SearchBox, { Criterion } from "../SearchBox"; +import { useCallback, useMemo, useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults/index"; +import EditNote from "@mui/icons-material/EditNote"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useRouter } from "next/navigation"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { PrinterResult } from "@/app/api/settings/printer"; +import { deletePrinter } from "@/app/api/settings/printer/actions"; +import PrinterSearchLoading from "./PrinterSearchLoading"; + +interface Props { + printers: PrinterResult[]; +} + +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const PrinterSearch: React.FC = ({ printers }) => { + const { t } = useTranslation("common"); + const [filteredPrinters, setFilteredPrinters] = useState(printers); + const [pagingController, setPagingController] = useState({ + pageNum: 1, + pageSize: 10, + }); + const router = useRouter(); + const [isSearching, setIsSearching] = useState(false); + + // Sync state when printers prop changes + useEffect(() => { + console.log("Printers prop changed:", printers); + setFilteredPrinters(printers); + }, [printers]); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("Name"), + paramName: "name", + type: "text", + }, + { + label: t("Code"), + paramName: "code", + type: "text", + }, + { + label: t("Type"), + paramName: "type", + type: "text", + }, + ], + [t], + ); + + const onPrinterClick = useCallback( + (printer: PrinterResult) => { + console.log(printer); + router.push(`/settings/printer/edit?id=${printer.id}`); + }, + [router], + ); + + const onDeleteClick = useCallback((printer: PrinterResult) => { + deleteDialog(async () => { + await deletePrinter(printer.id); + setFilteredPrinters(prev => prev.filter(p => p.id !== printer.id)); + router.refresh(); + successDialog(t("Delete Success") || "刪除成功", t); + }, t); + }, [t, router]); + + const columns = useMemo[]>( + () => [ + { + name: "action", + label: t("Edit"), + onClick: onPrinterClick, + buttonIcon: , + sx: { width: "10%", minWidth: "80px" }, + }, + { + name: "name", + label: t("Name"), + align: "left", + headerAlign: "left", + sx: { width: "20%", minWidth: "120px" }, + }, + { + name: "code", + label: t("Code"), + align: "left", + headerAlign: "left", + sx: { width: "15%", minWidth: "100px" }, + }, + { + name: "type", + label: t("Type"), + align: "left", + headerAlign: "left", + sx: { width: "15%", minWidth: "100px" }, + }, + { + name: "ip", + label: "IP", + align: "left", + headerAlign: "left", + sx: { width: "15%", minWidth: "100px" }, + }, + { + name: "port", + label: "Port", + align: "left", + headerAlign: "left", + sx: { width: "10%", minWidth: "80px" }, + }, + { + name: "action", + label: t("Delete"), + onClick: onDeleteClick, + buttonIcon: , + color: "error", + sx: { width: "10%", minWidth: "80px" }, + }, + ], + [t, onPrinterClick, onDeleteClick], + ); + + console.log("PrinterSearch render - filteredPrinters:", filteredPrinters); + console.log("PrinterSearch render - printers prop:", printers); + + return ( + <> + { + setIsSearching(true); + try { + let results: PrinterResult[] = printers; + + if (query.name && query.name.trim()) { + results = results.filter((printer) => + printer.name?.toLowerCase().includes(query.name?.toLowerCase() || "") + ); + } + + if (query.code && query.code.trim()) { + results = results.filter((printer) => + printer.code?.toLowerCase().includes(query.code?.toLowerCase() || "") + ); + } + + if (query.type && query.type.trim()) { + results = results.filter((printer) => + printer.type?.toLowerCase().includes(query.type?.toLowerCase() || "") + ); + } + + setFilteredPrinters(results); + setPagingController({ pageNum: 1, pageSize: pagingController.pageSize }); + } catch (error) { + console.error("Error searching printers:", error); + setFilteredPrinters(printers); + } finally { + setIsSearching(false); + } + }} + /> + + items={filteredPrinters} + columns={columns} + pagingController={pagingController} + setPagingController={setPagingController} + /> + + ); +}; + +export default PrinterSearch; diff --git a/src/components/PrinterSearch/PrinterSearchLoading.tsx b/src/components/PrinterSearch/PrinterSearchLoading.tsx new file mode 100644 index 0000000..85122c5 --- /dev/null +++ b/src/components/PrinterSearch/PrinterSearchLoading.tsx @@ -0,0 +1,39 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +export const PrinterSearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default PrinterSearchLoading; \ No newline at end of file diff --git a/src/components/PrinterSearch/PrinterSearchWrapper.tsx b/src/components/PrinterSearch/PrinterSearchWrapper.tsx new file mode 100644 index 0000000..5705b79 --- /dev/null +++ b/src/components/PrinterSearch/PrinterSearchWrapper.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import PrinterSearch from "./PrinterSearch"; +import PrinterSearchLoading from "./PrinterSearchLoading"; +import { PrinterResult, fetchPrinters } from "@/app/api/settings/printer"; + +interface SubComponents { + Loading: typeof PrinterSearchLoading; +} + +const PrinterSearchWrapper: React.FC & SubComponents = async () => { + let printers: PrinterResult[] = []; + try { + printers = await fetchPrinters(); + console.log("Printers fetched:", printers); + } catch (error) { + console.error("Error fetching printers:", error); + printers = []; + } + + return ; +}; + +PrinterSearchWrapper.Loading = PrinterSearchLoading; + +export default PrinterSearchWrapper; diff --git a/src/components/PrinterSearch/index.ts b/src/components/PrinterSearch/index.ts new file mode 100644 index 0000000..b042d0b --- /dev/null +++ b/src/components/PrinterSearch/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PrinterSearchWrapper"; + diff --git a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx index 7bed7ec..51f3efc 100644 --- a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx +++ b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx @@ -84,6 +84,10 @@ const QrCodeHandleWarehouseSearch: React.FC = ({ warehouses, printerCombo } }, [filteredPrinters, selectedPrinter]); + useEffect(() => { + setFilteredWarehouses(warehouses); + }, [warehouses]); + const handleReset = useCallback(() => { setSearchInputs({ store_id: "", diff --git a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx index 13b8870..fcce7a4 100644 --- a/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx +++ b/src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx @@ -1,21 +1,32 @@ import React from "react"; import QrCodeHandleWarehouseSearch from "./qrCodeHandleWarehouseSearch"; import QrCodeHandleSearchLoading from "./qrCodeHandleSearchLoading"; -import { fetchWarehouseList } from "@/app/api/warehouse"; -import { fetchPrinterCombo } from "@/app/api/settings/printer"; +import { fetchWarehouseList, WarehouseResult } from "@/app/api/warehouse"; +import { fetchPrinterCombo, PrinterCombo } from "@/app/api/settings/printer"; interface SubComponents { Loading: typeof QrCodeHandleSearchLoading; } const QrCodeHandleWarehouseSearchWrapper: React.FC & SubComponents = async () => { - const [warehouses, printerCombo] = await Promise.all([ - fetchWarehouseList(), - fetchPrinterCombo(), - ]); + let warehouses: WarehouseResult[] = []; + let printerCombo: PrinterCombo[] = []; + + try { + warehouses = await fetchWarehouseList(); + } catch (error) { + console.error("Error fetching warehouse list:", error); + } + + try { + printerCombo = await fetchPrinterCombo(); + } catch (error) { + console.error("Error fetching printer combo:", error); + } + return ; }; QrCodeHandleWarehouseSearchWrapper.Loading = QrCodeHandleSearchLoading; -export default QrCodeHandleWarehouseSearchWrapper; +export default QrCodeHandleWarehouseSearchWrapper; \ No newline at end of file diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 9d1b2d0..8214b33 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -421,5 +421,6 @@ "Edit shop details": "編輯店鋪詳情", "Add Shop to Truck Lane": "新增店鋪至卡車路線", "Truck lane code already exists. Please use a different code.": "卡車路線編號已存在,請使用其他編號。", - "MaintenanceEdit": "編輯維護和保養" + "MaintenanceEdit": "編輯維護和保養", + "Printer": "列印機" } \ No newline at end of file