Ver a proveniência

Update

MergeProblem1
B.E.N.S.O.N há 2 semanas
ascendente
cometimento
d09ee3a962
13 ficheiros alterados com 398 adições e 14 eliminações
  1. +47
    -0
      src/app/(main)/settings/printer/page.tsx
  2. +53
    -0
      src/app/api/settings/printer/actions.ts
  3. +19
    -2
      src/app/api/settings/printer/index.ts
  4. +1
    -1
      src/app/api/warehouse/index.ts
  5. +1
    -3
      src/app/utils/fetchUtil.ts
  6. +5
    -0
      src/components/NavigationContent/NavigationContent.tsx
  7. +182
    -0
      src/components/PrinterSearch/PrinterSearch.tsx
  8. +39
    -0
      src/components/PrinterSearch/PrinterSearchLoading.tsx
  9. +25
    -0
      src/components/PrinterSearch/PrinterSearchWrapper.tsx
  10. +2
    -0
      src/components/PrinterSearch/index.ts
  11. +4
    -0
      src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx
  12. +18
    -7
      src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx
  13. +2
    -1
      src/i18n/zh/common.json

+ 47
- 0
src/app/(main)/settings/printer/page.tsx Ver ficheiro

@@ -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 (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Printer")}
</Typography>
<Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="/settings/printer/create"
>
{t("Create Printer") || "新增列印機"}
</Button>
</Stack>
<I18nProvider namespaces={["common", "dashboard"]}>
<Suspense fallback={<PrinterSearch.Loading />}>
<PrinterSearch />
</Suspense>
</I18nProvider>
</>
);
};

export default Printer;

+ 53
- 0
src/app/api/settings/printer/actions.ts Ver ficheiro

@@ -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<PrinterResult>(`${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;
};

+ 19
- 2
src/app/api/settings/printer/index.ts Ver ficheiro

@@ -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<PrinterCombo[]>(`${BASE_API_URL}/printers/combo`, {
next: { tags: ["qcItems"] },
next: { tags: ["printers"] },
})
})
})

export const fetchPrinters = cache(async () => {
return serverFetchJson<PrinterResult[]>(`${BASE_API_URL}/printers`, {
next: { tags: ["printers"] },
});
});

+ 1
- 1
src/app/api/warehouse/index.ts Ver ficheiro

@@ -13,7 +13,7 @@ export interface WarehouseResult {
warehouse?: string;
area?: string;
slot?: string;
order?: number;
order?: string;
stockTakeSection?: string;
}



+ 1
- 3
src/app/utils/fetchUtil.ts Ver ficheiro

@@ -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<any, SessionWithTokens>(authOptions);
const accessToken = session?.accessToken;

@@ -129,7 +128,6 @@ export async function serverFetchBlob<T extends BlobResponse>(...args: FetchPara
while (!done) {
const read = await reader?.read();

// version 1
if (read?.done) {
done = true;
} else {


+ 5
- 0
src/components/NavigationContent/NavigationContent.tsx Ver ficheiro

@@ -362,6 +362,11 @@ const NavigationContent: React.FC = () => {
label: "QC Item All",
path: "/settings/qcItemAll",
},
{
icon: <QrCodeIcon/>,
label: "QR Code Handle",
path: "/settings/qrCodeHandle",
},
// {
// icon: <RequestQuote />,
// label: "Mail",


+ 182
- 0
src/components/PrinterSearch/PrinterSearch.tsx Ver ficheiro

@@ -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<Omit<PrinterResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const PrinterSearch: React.FC<Props> = ({ 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<SearchParamNames>[] = 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<Column<PrinterResult>[]>(
() => [
{
name: "action",
label: t("Edit"),
onClick: onPrinterClick,
buttonIcon: <EditNote />,
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: <DeleteIcon />,
color: "error",
sx: { width: "10%", minWidth: "80px" },
},
],
[t, onPrinterClick, onDeleteClick],
);

console.log("PrinterSearch render - filteredPrinters:", filteredPrinters);
console.log("PrinterSearch render - printers prop:", printers);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={async (query) => {
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);
}
}}
/>
<SearchResults<PrinterResult>
items={filteredPrinters}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
/>
</>
);
};

export default PrinterSearch;

+ 39
- 0
src/components/PrinterSearch/PrinterSearchLoading.tsx Ver ficheiro

@@ -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 (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default PrinterSearchLoading;

+ 25
- 0
src/components/PrinterSearch/PrinterSearchWrapper.tsx Ver ficheiro

@@ -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 <PrinterSearch printers={printers} />;
};

PrinterSearchWrapper.Loading = PrinterSearchLoading;

export default PrinterSearchWrapper;

+ 2
- 0
src/components/PrinterSearch/index.ts Ver ficheiro

@@ -0,0 +1,2 @@
export { default } from "./PrinterSearchWrapper";


+ 4
- 0
src/components/qrCodeHandles/qrCodeHandleWarehouseSearch.tsx Ver ficheiro

@@ -84,6 +84,10 @@ const QrCodeHandleWarehouseSearch: React.FC<Props> = ({ warehouses, printerCombo
}
}, [filteredPrinters, selectedPrinter]);

useEffect(() => {
setFilteredWarehouses(warehouses);
}, [warehouses]);

const handleReset = useCallback(() => {
setSearchInputs({
store_id: "",


+ 18
- 7
src/components/qrCodeHandles/qrCodeHandleWarehouseSearchWrapper.tsx Ver ficheiro

@@ -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 <QrCodeHandleWarehouseSearch warehouses={warehouses} printerCombo={printerCombo} />;
};

QrCodeHandleWarehouseSearchWrapper.Loading = QrCodeHandleSearchLoading;

export default QrCodeHandleWarehouseSearchWrapper;
export default QrCodeHandleWarehouseSearchWrapper;

+ 2
- 1
src/i18n/zh/common.json Ver ficheiro

@@ -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": "列印機"
}

Carregando…
Cancelar
Guardar