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")}
+
+ }
+ LinkComponent={Link}
+ href="/settings/printer/create"
+ >
+ {t("Create 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