Procházet zdrojové kódy

i18n+dashboard

master
CANCERYS\kw093 před 1 měsícem
rodič
revize
e24e83898a
23 změnil soubory, kde provedl 1021 přidání a 67 odebrání
  1. +4
    -1
      src/app/(main)/settings/m18ImportTesting/page.tsx
  2. +159
    -0
      src/app/api/dashboard/actions.ts
  3. +81
    -0
      src/app/api/dashboard/index.ts
  4. +32
    -0
      src/components/DashboardPage/CollapsibleCard.tsx
  5. +0
    -18
      src/components/DashboardPage/DashboardLineChart.tsx
  6. +55
    -22
      src/components/DashboardPage/DashboardPage.tsx
  7. +0
    -18
      src/components/DashboardPage/DashboardProgressChart.tsx
  8. +30
    -0
      src/components/DashboardPage/Dashboardbox.tsx
  9. +126
    -0
      src/components/DashboardPage/chart/ApplicationCompletionChart.tsx
  10. +74
    -0
      src/components/DashboardPage/chart/DashboardLineChart.tsx
  11. +128
    -0
      src/components/DashboardPage/chart/DashboardProgressChart.tsx
  12. +119
    -0
      src/components/DashboardPage/chart/OrderCompletionChart.tsx
  13. +65
    -0
      src/components/DashboardPage/chart/PendingInspectionChart.tsx
  14. +65
    -0
      src/components/DashboardPage/chart/PendingStorageChart.tsx
  15. +1
    -1
      src/components/M18ImportTesting/M18ImportDo.tsx
  16. +1
    -2
      src/components/M18ImportTesting/M18ImportMasterData.tsx
  17. +1
    -1
      src/components/M18ImportTesting/M18ImportPo.tsx
  18. +1
    -1
      src/components/M18ImportTesting/M18ImportPq.tsx
  19. +1
    -1
      src/components/M18ImportTesting/M18ImportTesting.tsx
  20. +31
    -2
      src/i18n/zh/dashboard.json
  21. +16
    -0
      src/i18n/zh/m18ImportTesting.json
  22. +22
    -0
      src/i18n/zh/mail.json
  23. +9
    -0
      src/i18n/zh/qcCategory.json

+ 4
- 1
src/app/(main)/settings/m18ImportTesting/page.tsx Zobrazit soubor

@@ -3,6 +3,7 @@ import { getServerI18n } from "@/i18n";
import { Stack } from "@mui/material";
import { Metadata } from "next";
import React, { Suspense } from "react";
import { I18nProvider } from "@/i18n";

export const metadata: Metadata = {
title: "Import Testing"
@@ -21,7 +22,9 @@ const M18ImportTestingPage: React.FC = async () => {
>
</Stack>
<Suspense fallback={<M18ImportTesting.Loading />}>
<M18ImportTesting />
<I18nProvider namespaces={["common", "m18ImportTesting"]}>
<M18ImportTesting />
</I18nProvider>
</Suspense>
</>
)


+ 159
- 0
src/app/api/dashboard/actions.ts Zobrazit soubor

@@ -0,0 +1,159 @@
"use server";
// import { BASE_API_URL } from "@/config/api";
import { BASE_API_URL } from "../../../config/api";
// import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache";
import { cache } from "react";
import { PoResult, StockInLine } from ".";
//import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson } from "../../utils/fetchUtil";
import { QcItemResult } from "../settings/qcItem";
import { RecordsRes } from "../utils";
// import { BASE_API_URL } from "@/config/api";

export interface PostStockInLiineResponse<T> {
id: number | null;
name: string;
code: string;
type?: string
message: string | null;
errorPosition: string | keyof T;
entity: T | T[]
// entity: StockInLine | StockInLine[]
}

export interface StockInLineEntry {
id?: number
itemId: number
purchaseOrderId: number
purchaseOrderLineId: number
acceptedQty: number
status?: string
expiryDate?: string
}

export interface PurchaseQcResult {
qcItemId: number;
failQty: number;
}
export interface StockInInput {
status: string
productLotNo?: string,
receiptDate: string
acceptedQty: number
acceptedWeight?: number
productionDate?: string
expiryDate: string
}
export interface PurchaseQCInput {
status: string
acceptedQty: number
sampleRate: number;
sampleWeight: number;
totalWeight: number;
qcResult: PurchaseQcResult[];
}
export interface EscalationInput {
status: string
handler: string
acceptedQty: number // this is the qty to be escalated
// escalationQty: number
}
export interface PutawayInput {
status: string
acceptedQty: number
warehouseId: number
// handler: string
// stockInLine: StockInLineEntry[]
}

export type ModalFormInput = Partial<PurchaseQCInput & StockInInput & EscalationInput & PutawayInput>

export const testFetch = cache(async (id: number) => {
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, {
next: { tags: ["po"] },
});
});

export const fetchStockInLineInfo = cache(async (stockInLineId: number) => {
return serverFetchJson<StockInLine>(`${BASE_API_URL}/stockInLine/${stockInLineId}`, {
next: { tags: ["stockInLine"] },
});
});

export const createStockInLine = async (data: StockInLineEntry) => {
const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/create`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
// revalidateTag("po");
return stockInLine
}

export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) => {
const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry & ModalFormInput>>(`${BASE_API_URL}/stockInLine/update`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
// revalidateTag("po");
return stockInLine
}

export const startPo = async (poId: number) => {
const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/start/${poId}`, {
method: "POST",
body: JSON.stringify({ poId }),
headers: { "Content-Type": "application/json" },
});
revalidateTag("po");
return po
}

export const checkPolAndCompletePo = async (poId: number) => {
const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/check/${poId}`, {
method: "POST",
body: JSON.stringify({ poId }),
headers: { "Content-Type": "application/json" },
});
revalidateTag("po");
return po
}

export const fetchPoInClient = cache(async (id: number) => {
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, {
next: { tags: ["po"] },
});
});

export const fetchPoListClient = cache(async (queryParams?: Record<string, any>) => {
if (queryParams) {
const queryString = new URLSearchParams(queryParams).toString();
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/list?${queryString}`, {
method: 'GET',
next: { tags: ["po"] },
});
} else {
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/list`, {
method: 'GET',
next: { tags: ["po"] },
});
}
});

export const testing = cache(async (queryParams?: Record<string, any>) => {
if (queryParams) {
const queryString = new URLSearchParams(queryParams).toString();
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing?${queryString}`, {
method: 'GET',
next: { tags: ["po"] },
});
} else {
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing`, {
method: 'GET',
next: { tags: ["po"] },
});
}
});


+ 81
- 0
src/app/api/dashboard/index.ts Zobrazit soubor

@@ -0,0 +1,81 @@
import { cache } from "react";
import "server-only";
// import { serverFetchJson } from "@/app/utils/fetchUtil";
// import { BASE_API_URL } from "@/config/api";
import { serverFetchJson } from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api";
import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils";

export interface PoResult {
id: number
code: string
orderDate: string
supplier: string
estimatedArrivalDate: string
completedDate: string
escalated: boolean
status: string
pol?: PurchaseOrderLine[]
}

export interface PurchaseOrderLine {
id: number
purchaseOrderId: number
itemId: number
itemNo: string
itemName: string
qty: number
processed: number
uom: Uom
price: number
status: string
stockInLine: StockInLine[]
}

export interface StockInLine {
id: number
stockInId: number
purchaseOrderId?: number
purchaseOrderLineId: number
itemId: number
itemNo: string
itemName: string
itemType: string
demandQty: number
acceptedQty: number
price: number
priceUnit: string
shelfLife?: number,
receiptDate?: string
productionDate?: string
expiryDate?: string
status: string
supplier: string
lotNo: string
poCode: string
uom: Uom
defaultWarehouseId: number // id for now
}


export const fetchPoList = cache(async (queryParams?: Record<string, any>) => {
if (queryParams) {
const queryString = new URLSearchParams(queryParams).toString();
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/list?${queryString}`, {
method: 'GET',
next: { tags: ["po"] },
});
} else {
return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/list`, {
method: 'GET',
next: { tags: ["po"] },
});
}
});

export const fetchPoWithStockInLines = cache(async (id: number) => {
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, {
next: { tags: ["po"] },
});
});

+ 32
- 0
src/components/DashboardPage/CollapsibleCard.tsx Zobrazit soubor

@@ -0,0 +1,32 @@
import React, { useState } from "react";
import { Card, CardHeader, CardContent, IconButton, Collapse } from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";

interface CollapsibleCardProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
}

const CollapsibleCard: React.FC<CollapsibleCardProps> = ({ title, children, defaultOpen = true }) => {
const [open, setOpen] = useState(defaultOpen);

return (
<Card>
<CardHeader
title={title}
action={
<IconButton onClick={() => setOpen((v) => !v)}>
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
}
/>
<Collapse in={open}>
<CardContent>{children}</CardContent>
</Collapse>
</Card>
);
};

export default CollapsibleCard;

+ 0
- 18
src/components/DashboardPage/DashboardLineChart.tsx Zobrazit soubor

@@ -1,18 +0,0 @@
"use client"
// npm install
interface Props { // params type

}

const DashboardLineChart: React.FC<Props> = ({
// params
}) => {

return (
<>
line chart
</>
)
}
export default DashboardLineChart


+ 55
- 22
src/components/DashboardPage/DashboardPage.tsx Zobrazit soubor

@@ -6,9 +6,14 @@ import { TabsProps } from "@mui/material/Tabs";
import React, { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Card, CardContent, CardHeader, Grid } from "@mui/material";
import DashboardProgressChart from "../DashboardProgressChart/DashboardProgressChart";
import DashboardLineChart from "../DashboardLineChart/DashboardLineChart";

import DashboardProgressChart from "./chart/DashboardProgressChart";
import DashboardLineChart from "./chart/DashboardLineChart";
import PendingInspectionChart from "./chart/PendingInspectionChart";
import PendingStorageChart from "./chart/PendingStorageChart";
import ApplicationCompletionChart from "./chart/ApplicationCompletionChart";
import OrderCompletionChart from "./chart/OrderCompletionChart";
import DashboardBox from "./DashboardBox";
import CollapsibleCard from "./CollapsibleCard";
type Props = {};

const DashboardPage: React.FC<Props> = ({}) => {
@@ -17,30 +22,58 @@ const DashboardPage: React.FC<Props> = ({}) => {

return (
<ThemeProvider theme={theme}>
<>
<Grid container>
<Grid item xs={12}>
<Card>
<CardHeader
title={t("Progress chart")}
/>
<CardContent>
<Grid container spacing={2}>
<Grid item xs={12}>
<CollapsibleCard title={t("Progress chart")}>
<CardContent>
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
<DashboardProgressChart />
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={4}>
<PendingInspectionChart />
</Grid>
<Grid item xs={12} md={4}>
<PendingStorageChart />
</Grid>
</Grid>
</CardContent>
</CollapsibleCard>
</Grid>

<Grid item xs={12}>
<CollapsibleCard title={t("Warehouse status")}>
<CardContent>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<ApplicationCompletionChart />
</Grid>
<Grid item xs={12} sm={6}>
<OrderCompletionChart />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Card>
<CardHeader
title={t("Line chart")}
/>
<CardContent>
<Grid item xs={12} md={6}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<DashboardBox title={t("Temperature status")} value="--" unit="°C" />
</Grid>
<Grid item xs={12} sm={6}>
<DashboardBox title={t("Humidity status")} value="--" unit="%" />
</Grid>
<Grid item xs={12}>
<DashboardLineChart />
</CardContent>
</Card>
</Grid>
</Grid>
</Grid>
</Grid>
</>
</CardContent>
</CollapsibleCard>
</Grid>
</Grid>
</ThemeProvider>
);
};


+ 0
- 18
src/components/DashboardPage/DashboardProgressChart.tsx Zobrazit soubor

@@ -1,18 +0,0 @@
"use client"

interface Props { // params type

}

const DashboardProgressChart: React.FC<Props> = ({
// params
}) => {

return (
<>
progress chart
</>
)
}
export default DashboardProgressChart


+ 30
- 0
src/components/DashboardPage/Dashboardbox.tsx Zobrazit soubor

@@ -0,0 +1,30 @@
"use client"
import React from "react";

interface DashboardBoxProps {
title: string;
value: string | number;
unit: string;
time?: string;
}

const DashboardBox: React.FC<DashboardBoxProps> = ({ title, value, unit, time }) => {
return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
marginBottom: 16
}}>
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 12 }}>{title}</div>
<div style={{ fontSize: 24, color: "#1976d2", marginBottom: 8 }}>
<span style={{ fontSize: 32, fontWeight: 700 }}>{value}</span> {unit}
</div>
<div style={{ color: "#888" }}>{time || "NaN/NaN/NaN NaN:NaN:NaN"}</div>
</div>
);
};

export default DashboardBox;

+ 126
- 0
src/components/DashboardPage/chart/ApplicationCompletionChart.tsx Zobrazit soubor

@@ -0,0 +1,126 @@
"use client"
import React, { useState } from "react";
import dynamic from "next/dynamic";
const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });
import { useTranslation } from "react-i18next";
const ApplicationCompletionChart: React.FC = () => {
const { t } = useTranslation();
const [tab, setTab] = useState(t("Raw material"));
const percent = 0;
const options = {
chart: { type: "donut" as const },
labels: [],
colors: ["#42A5F5", "#e0e0e0"],
dataLabels: {
enabled: true,
formatter: () => `${percent}%`,
style: { fontSize: "32px", fontWeight: 600, color: "#1976d2" }
},
legend: { show: false },
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: false
}
}
}
},
stroke: { show: true, width: 2, colors: ['#fff'] }
};

return (
<div
style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div
style={{
width: "100%",
fontWeight: 600,
fontSize: 18,
marginBottom: 12,
display: "flex",
alignItems: "center",
}}
>
<span
style={{
flex: 1,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
textAlign: "left",
}}
>
{t("Application completion")}
</span>
<div>
<button
style={{
border: tab === t("Raw material") ? "1px solid #1976d2" : "1px solid #e0e0e0",
background: tab === t("Raw material") ? "#fff" : "#f5f5f5",
color: tab === t("Raw material") ? "#1976d2" : "#333",
borderRadius: 4,
padding: "2px 12px",
marginRight: 4,
cursor: "pointer"
}}
onClick={() => setTab(t("Raw material"))}
>{t("Raw material")}</button>
<button
style={{
border: tab === t("Shipment") ? "1px solid #1976d2" : "1px solid #e0e0e0",
background: tab === t("Shipment") ? "#fff" : "#f5f5f5",
color: tab === t("Shipment") ? "#1976d2" : "#333",
borderRadius: 4,
padding: "2px 12px",
cursor: "pointer"
}}
onClick={() => setTab(t("Shipment"))}
>{t("Shipment")}</button>
</div>
</div>
<div
style={{
width: "100%",
height: 320,
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "0 auto",
}}
>
<ApexCharts
options={options}
series={[0, 100]}
type="donut"
width="100%"
height={280}
/>
</div>
<div style={{
marginTop: 16,
textAlign: "left",
border: "1px solid #e0e0e0",
borderRadius: 8,
padding: 12,
background: "#fafafa"
}}>
<div>{t("Processed application")}: 0</div>
<div>{t("Pending application")}: 0</div>
</div>
</div>
);
};

export default ApplicationCompletionChart;

+ 74
- 0
src/components/DashboardPage/chart/DashboardLineChart.tsx Zobrazit soubor

@@ -0,0 +1,74 @@
"use client"
// npm install
import { Select, MenuItem, FormControl, InputLabel } from "@mui/material";
import React,{useState} from "react";
import dynamic from "next/dynamic";
const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });
import { useTranslation } from "react-i18next";
interface Props { // params type

}

const DashboardLineChart: React.FC = () => {
const { t } = useTranslation();
const [warehouseType, setWarehouseType] = useState(t("Cold storage"));
const [timeRange, setTimeRange] = useState("6h");
const options = {
chart: { type: "line" as const },
xaxis: {
categories: ["10:00", "11:00", "12:00", "13:00", "14:00", "15:00"]
},
yaxis: {
title: { text: t("Temperature") }
},
stroke: { curve: "smooth" as const }
};
const series = [
{
name: "溫度",
data: []
}
];
return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
marginBottom: 16,
width: "100%",
}}>
<div style={{ display: "flex", alignItems: "center", marginBottom: 12 }}>
<span style={{ fontWeight: 600, fontSize: 18, flex: 1 }}>{t("Warehouse temperature record")}</span>
<FormControl size="small" style={{ minWidth: 100, marginRight: 8 }}>
<InputLabel>{t("Warehouse type")}</InputLabel>
<Select
value={warehouseType}
label={t("Warehouse type")}
onChange={e => setWarehouseType(e.target.value)}
>
<MenuItem value={t("Cold storage")}>{t("Cold storage")}</MenuItem>
<MenuItem value={t("Normal temperature storage")}>{t("Normal temperature storage")}</MenuItem>
</Select>
</FormControl>
<FormControl size="small" style={{ minWidth: 100 }}>
<Select
value={timeRange}
onChange={e => setTimeRange(e.target.value)}
>
<MenuItem value="6h">{t("Last 6 hours")}</MenuItem>
<MenuItem value="24h">{t("Last 24 hours")}</MenuItem>
</Select>
</FormControl>
</div>
<ApexCharts options={options} series={series} type="line" width="100%" height={220} />
</div>
);
};
export default DashboardLineChart


+ 128
- 0
src/components/DashboardPage/chart/DashboardProgressChart.tsx Zobrazit soubor

@@ -0,0 +1,128 @@
"use client"
import React, { useEffect, useState } from "react";
import dynamic from "next/dynamic";
import { PoResult } from "@/app/api/po";
import { fetchPoListClient } from "@/app/api/po/actions";
import { useTranslation } from "react-i18next";

interface Props { // params type
po: PoResult[];
}

const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });

const DashboardProgressChart: React.FC = () => {
const { t } = useTranslation("dashboard");
const [series, setSeries] = useState<number[]>([]);
const [total, setTotal] = useState(0);
const [pending, setPending] = useState(0);
const [receiving, setReceiving] = useState(0);
useEffect(() => {
const fetchData = async () => {
const res = await fetchPoListClient();
const records = res?.records || [];
const pendingCount = records.filter((r: any) => r.status === "pending").length;
const receivingCount = records.filter((r: any) => r.status === "receiving").length;
const totalCount = records.length;
setPending(pendingCount);
setReceiving(receivingCount);
setTotal(totalCount);
setSeries([pendingCount, receivingCount]);
};
fetchData();
}, []);
const options = {
chart: {
type: "donut" as const,
},
labels: [t("pending"), t("receiving")],
dataLabels: {
formatter: (val: number) => `${val.toFixed(1)}%`,
dropShadow: {
enabled: false,
},
style: {
fontSize: '18px',
fontWeight: 'bold',
},
},
legend: {
position: "bottom" as const,
fontSize: '16px',
markers: {
width: 16,
height: 16,
radius: 8,
},
},
colors: ["#A3C9F9", "#8DD7A9"],
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: true,
name: { show: false },
value: {
show: true,
fontSize: "32px",
fontWeight: 600,
color: "#333",
formatter: function (val: string) {
return `${val}%`;
}
},
total: {
show: false
}
}
}
}
},
stroke: {
show: true,
width: 2,
colors: ['#fff']
},
};
return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
maxWidth: 400,
margin: "0 ",
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)"
}}>
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 12 }}>{t("採購訂單概覽")}</div>
<div style={{ width: 320, height: 320, display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto" }}>
{series.length > 0 ? (
<ApexCharts
options={options}
series={series}
type="donut"
width={280}
height={280}
/>
) : (
<div style={{ height: 200, display: "flex", alignItems: "center", justifyContent: "center" }}>
載入中...
</div>
)}
</div>
<div style={{ marginTop: 16, textAlign: "left" }}>
<div>{t("pending")}:{pending}</div>
<div>{t("receiving")}:{receiving}</div>
<div>{t("total")}:{total}</div>
</div>
</div>
);
};

export default DashboardProgressChart

+ 119
- 0
src/components/DashboardPage/chart/OrderCompletionChart.tsx Zobrazit soubor

@@ -0,0 +1,119 @@
"use client"
import React, { useState } from "react";
import dynamic from "next/dynamic";
const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });
import { useTranslation } from "react-i18next";
const OrderCompletionChart: React.FC = () => {
const { t } = useTranslation();
const [tab, setTab] = useState(t("Raw material"));
const percent = 0;
const options = {
chart: { type: "donut" as const },
labels: [],
colors: ["#42A5F5", "#e0e0e0"],
dataLabels: {
enabled: true,
formatter: () => `${percent}%`,
style: { fontSize: "32px", fontWeight: 600, color: "#1976d2" }
},
legend: { show: false },
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: false
}
}
}
},
stroke: { show: true, width: 2, colors: ['#fff'] }
};

return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)"
}}>
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 12, display: "flex", alignItems: "center" }}>
<span style={{
flex: 1,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}>{t("Order completion")}</span>
<div>
<button
style={{
border: tab === t("Raw material") ? "1px solid #1976d2" : "1px solid #e0e0e0",
background: tab === t("Raw material") ? "#fff" : "#f5f5f5",
color: tab === t("Raw material") ? "#1976d2" : "#333",
borderRadius: 4,
padding: "2px 12px",
marginRight: 4,
cursor: "pointer"
}}
onClick={() => setTab(t("Raw material"))}
>{t("Raw material")}</button>
<button
style={{
border: tab === t("Consumable") ? "1px solid #1976d2" : "1px solid #e0e0e0",
background: tab === t("Consumable") ? "#fff" : "#f5f5f5",
color: tab === t("Consumable") ? "#1976d2" : "#333",
borderRadius: 4,
padding: "2px 12px",
marginRight: 4,
cursor: "pointer"
}}
onClick={() => setTab(t("Consumable"))}
>{t("Consumable")}</button>
<button
style={{
border: tab === t("Shipment") ? "1px solid #1976d2" : "1px solid #e0e0e0",
background: tab === t("Shipment") ? "#fff" : "#f5f5f5",
color: tab === t("Shipment") ? "#1976d2" : "#333",
borderRadius: 4,
padding: "2px 12px",
cursor: "pointer"
}}
onClick={() => setTab(t("Shipment"))}
>{t("Shipment")}</button>
</div>
</div>
<div
style={{
width: "100%",
height: 320,
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "0 auto",
}}
>
<ApexCharts
options={options}
series={[0, 100]}
type="donut"
width={280}
height={280}
/>
</div>
<div style={{
marginTop: 16,
textAlign: "left",
border: "1px solid #e0e0e0",
borderRadius: 8,
padding: 12,
background: "#fafafa"
}}>
<div>{t("Extracted order")}: 0</div>
<div>{t("Pending order")}: 0</div>
</div>
</div>
);
};

export default OrderCompletionChart;

+ 65
- 0
src/components/DashboardPage/chart/PendingInspectionChart.tsx Zobrazit soubor

@@ -0,0 +1,65 @@
"use client"
import React from "react";
import dynamic from "next/dynamic";
const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });
import { useTranslation } from "react-i18next";

const PendingInspectionChart: React.FC = () => {
const { t } = useTranslation("dashboard");
const percent = 6.25;
const options = {
chart: { type: "donut" as const },
labels: [t("pending inspection material"), t("inspected material")],
colors: ["#2196f3", "#e0e0e0"],
dataLabels: { enabled: false },
legend: { position: "bottom" as const },
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: true,
name: { show: false },
value: {
show: true,
fontSize: "32px",
fontWeight: 600,
color: "#1976d2",
formatter: () => `${percent}%`
},
total: { show: false }
}
}
}
},
stroke: { show: true, width: 2, colors: ['#fff'] }
};

return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)"
}}>
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 12 }}>待品檢物料</div>
<div style={{ width: 320, height: 320, display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto" }}>
<ApexCharts
options={options}
series={[1, 15]}
type="donut"
width={280}
height={280}
/>
</div>
<div style={{ marginTop: 16, textAlign: "left" }}>
<div>{t("pending inspection material")}: 1</div>
<div>{t("total material")}: 16</div>
<div>{t("inspected material")}: 15</div>
</div>
</div>
);
};

export default PendingInspectionChart;

+ 65
- 0
src/components/DashboardPage/chart/PendingStorageChart.tsx Zobrazit soubor

@@ -0,0 +1,65 @@
"use client"
import React from "react";
import dynamic from "next/dynamic";
import { useTranslation } from "node_modules/react-i18next";
const ApexCharts = dynamic(() => import("react-apexcharts"), { ssr: false });

const PendingStorageChart: React.FC = () => {
const { t } = useTranslation();
const percent = 93.75;
const options = {
chart: { type: "donut" as const },
labels: [t("Pending storage"), t("Total storage")],
colors: ["#1976d2", "#e0e0e0"],
dataLabels: { enabled: false },
legend: { position: "bottom" as const },
plotOptions: {
pie: {
donut: {
size: "70%",
labels: {
show: true,
name: { show: false },
value: {
show: true,
fontSize: "32px",
fontWeight: 600,
color: "#1976d2",
formatter: () => `${percent}%`
},
total: { show: false }
}
}
}
},
stroke: { show: true, width: 2, colors: ['#fff'] }
};

return (
<div style={{
border: "1px solid #e0e0e0",
borderRadius: 12,
padding: 24,
background: "#fff",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)"
}}>
<div style={{ fontWeight: 600, fontSize: 18, marginBottom: 12 }}>{t("Pending storage")}</div>
<div style={{ width: 320, height: 320, display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto" }}>
<ApexCharts
options={options}
series={[15, 1]}
type="donut"
width={280}
height={280}
/>
</div>
<div style={{ marginTop: 16, textAlign: "left" }}>
<div>&nbsp;</div> {t("Pending storage")}: 15
<div>{t("Total storage")}: 16</div>
</div>
</div>
);
};

export default PendingStorageChart;

+ 1
- 1
src/components/M18ImportTesting/M18ImportDo.tsx Zobrazit soubor

@@ -16,7 +16,7 @@ interface Props {
const M18ImportDo: React.FC<Props> = ({
}) => {

const { t } = useTranslation()
const { t } = useTranslation("m18ImportTesting")
const [isLoading, setIsLoading] = useState(false)
const {
control,


+ 1
- 2
src/components/M18ImportTesting/M18ImportMasterData.tsx Zobrazit soubor

@@ -15,8 +15,7 @@ interface Props {

const M18ImportMasterData: React.FC<Props> = ({
}) => {

const { t } = useTranslation()
const { t } = useTranslation("m18ImportTesting")
const [isLoading, setIsLoading] = useState(false)
const {
control,


+ 1
- 1
src/components/M18ImportTesting/M18ImportPo.tsx Zobrazit soubor

@@ -16,7 +16,7 @@ interface Props {
const M18ImportPo: React.FC<Props> = ({
}) => {

const { t } = useTranslation("settings")
const { t } = useTranslation("m18ImportTesting")
const [isLoading, setIsLoading] = useState(false)
const {
control,


+ 1
- 1
src/components/M18ImportTesting/M18ImportPq.tsx Zobrazit soubor

@@ -16,7 +16,7 @@ interface Props {
const M18ImportPq: React.FC<Props> = ({
}) => {

const { t } = useTranslation()
const { t } = useTranslation("m18ImportTesting")
const [isLoading, setIsLoading] = useState(false)
const {
control,


+ 1
- 1
src/components/M18ImportTesting/M18ImportTesting.tsx Zobrazit soubor

@@ -19,7 +19,7 @@ const M18ImportTesting: React.FC<Props> = ({

}) => {

const { t } = useTranslation()
const { t } = useTranslation("m18ImportTesting")
const [isLoading, setIsLoading] = useState(false)
const [loadingType, setLoadingType] = useState<String | null>(null)
const formProps = useForm<M18ImportTestingForm>()


+ 31
- 2
src/i18n/zh/dashboard.json Zobrazit soubor

@@ -1,4 +1,33 @@
{
"Dashboard": "資訊展示面板",
"Order status": "訂單狀態"
}
"Order status": "訂單狀態",
"pending": "未收貨",
"receiving": "收貨中",
"total": "未完成總數",
"Warehouse temperature record": "倉庫溫度記錄",
"Warehouse type": "倉庫類型",
"Last 6 hours": "過去6小時",
"Last 24 hours": "過去24小時",
"Cold storage": "冷藏倉",
"Normal temperature storage": "常溫倉",
"Temperature status": "溫度狀態",
"Humidity status": "濕度狀態",
"Warehouse status": "倉庫狀態",
"Progress chart": "進度圖表",
"Order completion": "訂單完成度",
"Raw material": "原料",
"Consumable": "消耗品",
"Shipment": "出貨",
"Extracted order": "已提取提料單",
"Pending order": "待提取提料單",
"Temperature": "溫度",
"Humidity": "濕度",
"Pending storage": "待入倉物料",
"Total storage": "已入倉物料",
"Application completion": "提料申請完成度",
"Processed application": "已處理提料申請",
"Pending application": "待處理提料申請",
"pending inspection material": "待品檢物料",
"inspected material": "已品檢物料",
"total material": "物料總數"
}

+ 16
- 0
src/i18n/zh/m18ImportTesting.json Zobrazit soubor

@@ -0,0 +1,16 @@
{
"Import Master Data": "匯入主資料",
"Modified Date From": "修改日期從",
"Modified Date From *": "修改日期從 *",
"Modified Date To": "修改日期到",
"Modified Date To *": "修改日期到 *",
"Import Purchase Order": "匯入採購單",
"Import Delivery Order": "匯入出貨單",
"Import Purchase Quotation": "匯入採購報價單",
"Import Po": "匯入採購單",
"Import Do": "匯入出貨單",
"Import Pq": "匯入採購報價單",
"Ready to import": "準備匯入",
"Status": "狀態"

}

+ 22
- 0
src/i18n/zh/mail.json Zobrazit soubor

@@ -0,0 +1,22 @@
{
"Mail": "郵件",
"Mail List": "郵件列表",
"Mail Name": "郵件名稱",
"Mail Description": "郵件描述",
"Mail Status": "郵件狀態",
"Mail Created At": "郵件創建時間",
"Mail Updated At": "郵件更新時間",
"Setting": "設定",
"Settings": "設定",
"Template": "模板",
"Code": "代碼",
"Description": "描述",
"Subject CHT": "主旨 (繁體中文)",
"Select Template (View By Code - Description)": "選擇模板 (代碼 - 描述)",
"MAIL.smtp.host": "SMTP 主機",
"MAIL.smtp.port": "SMTP 埠口",
"MAIL.smtp.username": "SMTP 使用者名稱",
"MAIL.smtp.password": "SMTP 密碼",
"MAIL.smtp.auth": "SMTP 認證",
"MAIL.smtp.ssl": "SMTP SSL"
}

+ 9
- 0
src/i18n/zh/qcCategory.json Zobrazit soubor

@@ -0,0 +1,9 @@
{
"Qc Category": "QC 類別",
"Qc Category List": "QC 類別列表",
"Qc Category Name": "QC 類別名稱",
"Qc Category Description": "QC 類別描述",
"Qc Category Status": "QC 類別狀態",
"Qc Category Created At": "QC 類別創建時間",
"Qc Category Updated At": "QC 類別更新時間"
}

Načítá se…
Zrušit
Uložit