Browse Source

Merge branch 'master' of https://git.2fi-solutions.com/derek/FPSMS-frontend

# Conflicts:
#	src/app/api/pickOrder/actions.ts
master
CANCERYS\kw093 2 days ago
parent
commit
2caff3ecc1
33 changed files with 744 additions and 496 deletions
  1. +3
    -0
      src/app/(main)/dashboard/page.tsx
  2. +2
    -2
      src/app/(main)/layout.tsx
  3. +4
    -0
      src/app/(main)/po/edit/page.tsx
  4. +0
    -3
      src/app/api/dashboard/actions.ts
  5. +5
    -4
      src/app/api/dashboard/index.ts
  6. +58
    -0
      src/app/api/escalation/index.ts
  7. +17
    -0
      src/app/api/mailTemplate/actions.ts
  8. +0
    -41
      src/app/api/pickOrder/actions.ts
  9. +9
    -5
      src/app/api/po/actions.ts
  10. +18
    -11
      src/app/api/po/index.ts
  11. +10
    -0
      src/app/api/qc/index.ts
  12. +15
    -0
      src/app/api/user/index.ts
  13. +6
    -1
      src/app/utils/fetchUtil.ts
  14. +9
    -5
      src/components/DashboardPage/DashboardPage.tsx
  15. +10
    -5
      src/components/DashboardPage/DashboardWrapper.tsx
  16. +76
    -19
      src/components/DashboardPage/escalation/EscalationLogTable.tsx
  17. +19
    -0
      src/components/General/LoadingComponent.tsx
  18. +1
    -0
      src/components/InputDataGrid/InputDataGrid.tsx
  19. +1
    -1
      src/components/PickOrderSearch/QcFormVer2.tsx
  20. +41
    -22
      src/components/PoDetail/EscalationComponent.tsx
  21. +77
    -43
      src/components/PoDetail/PoDetail.tsx
  22. +8
    -1
      src/components/PoDetail/PoDetailWrapper.tsx
  23. +1
    -1
      src/components/PoDetail/PoInfoCard.tsx
  24. +25
    -16
      src/components/PoDetail/PoInputGrid.tsx
  25. +49
    -42
      src/components/PoDetail/QcFormVer2.tsx
  26. +53
    -55
      src/components/PoDetail/QcStockInModalVer2.tsx
  27. +7
    -7
      src/components/PoDetail/StockInFormVer2.tsx
  28. +30
    -24
      src/components/PoDetail/dummyQcTemplate.tsx
  29. +1
    -1
      src/i18n/zh/common.json
  30. +45
    -38
      src/i18n/zh/dashboard.json
  31. +2
    -2
      src/i18n/zh/inventory.json
  32. +134
    -146
      src/i18n/zh/purchaseOrder.json
  33. +8
    -1
      src/theme/devias-material-kit/components.ts

+ 3
- 0
src/app/(main)/dashboard/page.tsx View File

@@ -4,6 +4,7 @@ import { Suspense } from "react";
import { getServerI18n } from "@/i18n"; import { getServerI18n } from "@/i18n";
import DashboardPage from "@/components/DashboardPage"; import DashboardPage from "@/components/DashboardPage";
import { SearchParams } from "@/app/utils/fetchUtil"; import { SearchParams } from "@/app/utils/fetchUtil";
import { fetchEscalationLogsByUser } from "@/app/api/escalation";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Dashboard", title: "Dashboard",
@@ -14,6 +15,8 @@ type Props = {} & SearchParams;
const Dashboard: React.FC<Props> = async ({ searchParams }) => { const Dashboard: React.FC<Props> = async ({ searchParams }) => {
const { t } = await getServerI18n("dashboard"); const { t } = await getServerI18n("dashboard");


fetchEscalationLogsByUser()

return ( return (
<I18nProvider namespaces={["dashboard", "common"]}> <I18nProvider namespaces={["dashboard", "common"]}>
<Suspense fallback={<DashboardPage.Loading />}> <Suspense fallback={<DashboardPage.Loading />}>


+ 2
- 2
src/app/(main)/layout.tsx View File

@@ -37,7 +37,7 @@ export default async function MainLayout({
return ( return (
<SessionProviderWrapper session={session}> <SessionProviderWrapper session={session}>
<UploadProvider> <UploadProvider>
<CameraProvider>
{/* <CameraProvider> */}
<AxiosProvider> <AxiosProvider>
<QrCodeScannerProvider> <QrCodeScannerProvider>
<> <>
@@ -62,7 +62,7 @@ export default async function MainLayout({
</> </>
</QrCodeScannerProvider> </QrCodeScannerProvider>
</AxiosProvider> </AxiosProvider>
</CameraProvider>
{/* </CameraProvider> */}
</UploadProvider> </UploadProvider>
</SessionProviderWrapper> </SessionProviderWrapper>
); );


+ 4
- 0
src/app/(main)/po/edit/page.tsx View File

@@ -1,3 +1,4 @@
import { fetchEscalationCombo } from "@/app/api/user";
import { SearchParams } from "@/app/utils/fetchUtil"; import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum"; import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem"; import CreateProductMaterial from "@/components/CreateItem";
@@ -22,6 +23,9 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => {
if (!id) { if (!id) {
notFound(); notFound();
} }

fetchEscalationCombo()

return ( return (
<> <>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} {/* <Typography variant="h4">{t("Create Material")}</Typography> */}


+ 0
- 3
src/app/api/dashboard/actions.ts View File

@@ -30,9 +30,6 @@ export interface StockInLineEntry {
acceptedQty: number; acceptedQty: number;
status?: string; status?: string;
expiryDate?: string; expiryDate?: string;
productLotNo?: string;
receiptDate?: string;
dnDate?: string;
} }


export interface PurchaseQcResult { export interface PurchaseQcResult {


+ 5
- 4
src/app/api/dashboard/index.ts View File

@@ -6,7 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api"; import { BASE_API_URL } from "../../../config/api";
import { Uom } from "../settings/uom"; import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils"; import { RecordsRes } from "../utils";
import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval";
// import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval";


export interface PoResult { export interface PoResult {
id: number; id: number;
@@ -84,7 +84,8 @@ export const fetchPoWithStockInLines = cache(async (id: number) => {
}); });


export const fetchIqcLogByUser = cache(async () => { export const fetchIqcLogByUser = cache(async () => {
return serverFetchJson<IQCItems[]>(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, {
next: { tags: ["qcLog"] },
});
// return serverFetchJson<IQCItems[]>(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, {
// next: { tags: ["qcLog"] },
// });
return undefined;
}); });

+ 58
- 0
src/app/api/escalation/index.ts View File

@@ -0,0 +1,58 @@
"server only"

import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface EscalationResult {
id: number;
handler?: string;
handlerDepartment?: string;
handlerName?: string;
handlerTitle?: string;
polId?: number;
poId?: number;
reason?: string;
handlerId?: number;
itemName?: string;
demandQty?: number;
acceptedQty?: number;
purchaseUomCode?: string;
purchaseUomDesc?: string;
stockUomCode?: string;
stockUomDesc?: string;
stockInLineId?: number;
stockOutLineId?: number;
qcFailCount?: number;
qcTotalCount?: number;
poCode?: string;
itemCode?: string;
dnDate?: number[];
dnNo?: string;
}

export const fetchEscalationLogsByStockInLines = cache(async(stockInLineIds: number[]) => {
const searchParams = convertObjToURLSearchParams({stockInLineIds: stockInLineIds})
return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/stockInLines?${searchParams}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["escalationLogs"],
},
},
);
});

export const fetchEscalationLogsByUser = cache(async() => {
return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/user`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["escalationLogs"],
},
},
);
});

+ 17
- 0
src/app/api/mailTemplate/actions.ts View File

@@ -0,0 +1,17 @@
"use server";

import { serverFetchBlob } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export const getMailTemplateForStockInLine = cache(async (stockInLineId: number) => {
console.log("stockInLineId", stockInLineId)
return serverFetchBlob(`${BASE_API_URL}/mailTemplates/getMailTemplateForStockInLine/${stockInLineId}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["mailTemplateForStockInLine"],
},
})
})

+ 0
- 41
src/app/api/pickOrder/actions.ts View File

@@ -144,47 +144,6 @@ export interface PickOrderLotDetailResponse {
} }




export interface AssignPickOrderInputs {
pickOrderIds: number[];
assignTo: number;
}
export const newassignPickOrder = async (data: AssignPickOrderInputs) => {
const pickOrder = await serverFetchJson<any>(
`${BASE_API_URL}/pickOrder/assign`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("pickorder");
return pickOrder;
};

export const newreleasePickOrder = async (data: AssignPickOrderInputs) => {
const pickOrder = await serverFetchJson<any>(
`${BASE_API_URL}/pickOrder/release`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("pickorder");
return pickOrder;
};
export const releaseAssignedPickOrders = async (data: AssignPickOrderInputs) => {
const pickOrder = await serverFetchJson<any>(
`${BASE_API_URL}/pickOrder/release-assigned`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
},
);
revalidateTag("pickorder");
return pickOrder;
};
export const fetchAllPickOrderDetails = cache(async () => { export const fetchAllPickOrderDetails = cache(async () => {
return serverFetchJson<GetPickOrderInfoResponse>( return serverFetchJson<GetPickOrderInfoResponse>(
`${BASE_API_URL}/pickOrder/detail`, `${BASE_API_URL}/pickOrder/detail`,


+ 9
- 5
src/app/api/po/actions.ts View File

@@ -31,13 +31,17 @@ export interface StockInLineEntry {
acceptedQty: number; acceptedQty: number;
status?: string; status?: string;
expiryDate?: string; expiryDate?: string;
productLotNo?: string;
receiptDate?: string;
dnDate?: string;
} }


export interface PurchaseQcResult {
export interface PurchaseQcResult{
qcItemId: number; qcItemId: number;
isPassed: boolean,
qcPassed: boolean;
failQty: number; failQty: number;
remarks?: string
remarks?: string;
} }
export interface StockInInput { export interface StockInInput {
status: string; status: string;
@@ -110,7 +114,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => {


export const createStockInLine = async (data: StockInLineEntry) => { export const createStockInLine = async (data: StockInLineEntry) => {
const stockInLine = await serverFetchJson< const stockInLine = await serverFetchJson<
PostStockInLineResponse<StockInLineEntry>
PostStockInLineResponse<StockInLine>
>(`${BASE_API_URL}/stockInLine/create`, { >(`${BASE_API_URL}/stockInLine/create`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
@@ -124,7 +128,7 @@ export const updateStockInLine = async (
data: StockInLineEntry & ModalFormInput, data: StockInLineEntry & ModalFormInput,
) => { ) => {
const stockInLine = await serverFetchJson< const stockInLine = await serverFetchJson<
PostStockInLineResponse<StockInLineEntry & ModalFormInput>
PostStockInLineResponse<StockInLine & ModalFormInput>
>(`${BASE_API_URL}/stockInLine/update`, { >(`${BASE_API_URL}/stockInLine/update`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),


+ 18
- 11
src/app/api/po/index.ts View File

@@ -15,6 +15,11 @@ export interface PoResult {
estimatedArrivalDate: string; estimatedArrivalDate: string;
completedDate: string; completedDate: string;
itemDetail?: string; itemDetail?: string;
itemCode?: string;
itemName?: string;
itemQty?: string;
itemSumAcceptedQty?: string;
itemUom?: string;
escalated: boolean; escalated: boolean;
status: string; status: string;
pol?: PurchaseOrderLine[]; pol?: PurchaseOrderLine[];
@@ -49,7 +54,7 @@ export interface StockUomForPoLine {


export interface StockInLine { export interface StockInLine {
id: number; id: number;
stockInId: number;
stockInId?: number;
purchaseOrderId?: number; purchaseOrderId?: number;
purchaseOrderLineId: number; purchaseOrderLineId: number;
itemId: number; itemId: number;
@@ -58,22 +63,24 @@ export interface StockInLine {
itemType: string; itemType: string;
demandQty: number; demandQty: number;
acceptedQty: number; acceptedQty: number;
qty: number;
processed: number;
price: number;
priceUnit: string;
qty?: number;
processed?: number;
price?: number;
priceUnit?: string;
shelfLife?: number; shelfLife?: number;
receiptDate?: string; receiptDate?: string;
productionDate?: string; productionDate?: string;
productLotNo?: string;
expiryDate?: string; expiryDate?: string;
status: string; status: string;
supplier: string;
lotNo: string;
poCode: string;
uom: Uom;
supplier?: string;
lotNo?: string;
poCode?: string;
uom?: Uom;
defaultWarehouseId: number; // id for now defaultWarehouseId: number; // id for now
dnNo: string;
dnDate: number[];
dnNo?: string;
dnDate?: number[];
stockQty?: number;
} }


export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { export const fetchPoList = cache(async (queryParams?: Record<string, any>) => {


+ 10
- 0
src/app/api/qc/index.ts View File

@@ -15,6 +15,16 @@ export interface QcItemWithChecks {
description: string | undefined; description: string | undefined;
} }


export interface QcData {
id: number,
code: string,
name: string,
qcDescription: string,
qcPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
}

export const fetchQcItemCheckList = cache(async () => { export const fetchQcItemCheckList = cache(async () => {
return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, {
next: { tags: ["qc"] }, next: { tags: ["qc"] },


+ 15
- 0
src/app/api/user/index.ts View File

@@ -31,6 +31,15 @@ export type passwordRule = {
specialChar: boolean; specialChar: boolean;
}; };


export interface EscalationCombo {
id: number;
value: number;
label: string;
name: string;
title: string;
department: string;
}

export const preloadUser = () => { export const preloadUser = () => {
fetchUser(); fetchUser();
}; };
@@ -56,3 +65,9 @@ export const fetchPwRules = cache(async () => {
next: { tags: ["pwRule"] }, next: { tags: ["pwRule"] },
}); });
}); });

export const fetchEscalationCombo = cache(async () => {
return serverFetchJson<EscalationCombo>(`${BASE_API_URL}/user/escalation-combo`, {
next: { tags: ["escalationCombo"]}
})
})

+ 6
- 1
src/app/utils/fetchUtil.ts View File

@@ -3,6 +3,11 @@ import { getServerSession } from "next-auth";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";


export interface BlobResponse {
filename: string;
blobValue: Uint8Array;
}

export interface Pageable { export interface Pageable {
pageSize?: number; pageSize?: number;
pageNum?: number; pageNum?: number;
@@ -86,7 +91,7 @@ export async function serverFetchJson<T>(...args: FetchParams) {
} }
} }


export async function serverFetchBlob<T>(...args: FetchParams) {
export async function serverFetchBlob<T extends BlobResponse>(...args: FetchParams) {
const response = await serverFetch(...args); const response = await serverFetch(...args);


if (response.ok) { if (response.ok) {


+ 9
- 5
src/components/DashboardPage/DashboardPage.tsx View File

@@ -14,13 +14,17 @@ import ApplicationCompletionChart from "./chart/ApplicationCompletionChart";
import OrderCompletionChart from "./chart/OrderCompletionChart"; import OrderCompletionChart from "./chart/OrderCompletionChart";
import DashboardBox from "./Dashboardbox"; import DashboardBox from "./Dashboardbox";
import CollapsibleCard from "./CollapsibleCard"; import CollapsibleCard from "./CollapsibleCard";
import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval";
// import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval";
import { EscalationResult } from "@/app/api/escalation";
import EscalationLogTable from "./escalation/EscalationLogTable";
type Props = { type Props = {
iqc: IQCItems[]
// iqc: IQCItems[] | undefined
escalationLogs: EscalationResult[]
}; };


const DashboardPage: React.FC<Props> = ({ const DashboardPage: React.FC<Props> = ({
iqc
// iqc,
escalationLogs
}) => { }) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const router = useRouter(); const router = useRouter();
@@ -29,9 +33,9 @@ const DashboardPage: React.FC<Props> = ({
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<CollapsibleCard title={t("stock in escalation list")}>
<CollapsibleCard title={t("Escalation List")}>
<CardContent> <CardContent>
<SupervisorQcApproval items={iqc || []}/>
<EscalationLogTable items={escalationLogs || []}/>
</CardContent> </CardContent>
</CollapsibleCard> </CollapsibleCard>
</Grid> </Grid>


+ 10
- 5
src/components/DashboardPage/DashboardWrapper.tsx View File

@@ -4,7 +4,8 @@ import DashboardPage from "./DashboardPage";
import { Typography } from "@mui/material"; import { Typography } from "@mui/material";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import DashboardLoading from "./DashboardLoading"; import DashboardLoading from "./DashboardLoading";
import { fetchIqcLogByUser } from "@/app/api/dashboard";
import { fetchEscalationLogsByUser } from "@/app/api/escalation";
// import { fetchIqcLogByUser } from "@/app/api/dashboard";


// export type SessionWithAbilities = { // export type SessionWithAbilities = {
// abilities: string[] // abilities: string[]
@@ -23,16 +24,20 @@ const DashboardWrapper: React.FC<Props> & SubComponents = async ({
}) => { }) => {
const { t } = await getServerI18n("dashboard"); const { t } = await getServerI18n("dashboard");
// const session: SessionWithAbilities = await getServerSession(authOptions) // const session: SessionWithAbilities = await getServerSession(authOptions)
const [iqcLog] = await Promise.all([
fetchIqcLogByUser()
// const [iqcLog] = await Promise.all([
// fetchIqcLogByUser()
// ])

const [escalationLogs] = await Promise.all([
fetchEscalationLogsByUser()
]) ])
console.log(iqcLog)
return ( return (
<> <>
<Typography variant="h4">{t("Dashboard")}</Typography> <Typography variant="h4">{t("Dashboard")}</Typography>
<DashboardPage <DashboardPage
iqc={iqcLog}
escalationLogs={escalationLogs}
// iqc={iqcLog}
// abilities={session ? session?.abilities : []} // abilities={session ? session?.abilities : []}
/> />
</> </>


src/components/DashboardPage/QC/SupervisorQcApproval.tsx → src/components/DashboardPage/escalation/EscalationLogTable.tsx View File

@@ -2,9 +2,13 @@


import { Box, Card, CardActionArea, CardContent, CardHeader, Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; import { Box, Card, CardActionArea, CardContent, CardHeader, Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { EscalationResult } from "@/app/api/escalation";
import { Column } from "@/components/SearchResults";
import SearchResults from "@/components/SearchResults/SearchResults";
import { arrayToDateString } from "@/app/utils/formatUtil";


export type IQCItems = { export type IQCItems = {
id: number; id: number;
@@ -18,11 +22,11 @@ export type IQCItems = {
}; };


type Props = { type Props = {
items: IQCItems[];
items: EscalationResult[];
}; };


const SupervisorQcApproval: React.FC<Props> = ({
items
const EscalationLogTable: React.FC<Props> = ({
items
}) => { }) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const CARD_HEADER = t("stock in escalation list") const CARD_HEADER = t("stock in escalation list")
@@ -31,8 +35,8 @@ const SupervisorQcApproval: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const [selectedId, setSelectedId] = useState<number | null>(null); const [selectedId, setSelectedId] = useState<number | null>(null);


const navigateTo = useCallback(
(item: IQCItems) => {
const navigateTo = useCallback(
(item: EscalationResult) => {
setSelectedId(item.id); setSelectedId(item.id);
console.log(pathname) console.log(pathname)
router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`);
@@ -40,17 +44,62 @@ const navigateTo = useCallback(
[router, pathname] [router, pathname]
); );


const handleKeyDown = useCallback(
(e: React.KeyboardEvent, item: IQCItems) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
navigateTo(item);
}
},
[navigateTo]
);
const onRowClick = useCallback((item: EscalationResult) => {
router.push(`/po/edit?id=${item.poId}&selectedIds=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`);
}, [router])

// const handleKeyDown = useCallback(
// (e: React.KeyboardEvent, item: EscalationResult) => {
// if (e.key === 'Enter' || e.key === ' ') {
// e.preventDefault();
// navigateTo(item);
// }
// },
// [navigateTo]
// );


return (
const columns = useMemo<Column<EscalationResult>[]>(
() => [
{
name: "handler",
label: t("Responsible for handling colleagues")
},
{
name: "acceptedQty",
label: t("Received Qty"),
align: "right",
headerAlign: "right"
},
{
name: "purchaseUomDesc",
label: t("Purchase UoM")
},
{
name: "dnDate",
label: t("DN Date"),
renderCell: (params) => {
return params.dnDate ? arrayToDateString(params.dnDate) : "N/A"
}
},
{
name: "qcTotalCount",
label: t("QC Completed Count"),
align: "right",
headerAlign: "right"
},
{
name: "qcFailCount",
label: t("QC Fail Count"),
align: "right",
headerAlign: "right"
},
{
name: "reason",
label: t("Reason"),
},
], [])

{/* return (
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table aria-label="Two column navigable table" size="small"> <Table aria-label="Two column navigable table" size="small">
<TableHead> <TableHead>
@@ -87,7 +136,15 @@ const navigateTo = useCallback(
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
);
};
);*/}
return (
<SearchResults
onRowClick={onRowClick}
items={items}
columns={columns}
isAutoPaging={false}
/>
)
};


export default SupervisorQcApproval;
export default EscalationLogTable;

+ 19
- 0
src/components/General/LoadingComponent.tsx View File

@@ -0,0 +1,19 @@
import {Box, CircularProgress, Grid} from "@mui/material";

export const LoadingComponent: React.FC = () => {
return (
<>
<Grid item xs={12} md={12} lg={12} justifyContent="space-between" alignItems="center" marginTop={10}>
<Box
display="flex"
justifyContent="center"
alignItems="center"
// autoheight="true"
>
<CircularProgress />
</Box>
</Grid>
</>
)
}
export default LoadingComponent;

+ 1
- 0
src/components/InputDataGrid/InputDataGrid.tsx View File

@@ -84,6 +84,7 @@ export interface SelectionInputDataGridProps<T, V, E> {
columns: GridColDef[]; columns: GridColDef[];
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
needAdd?: boolean; needAdd?: boolean;
showRemoveBtn?: boolean;
} }


export type Props<T, V, E> = export type Props<T, V, E> =


+ 1
- 1
src/components/PickOrderSearch/QcFormVer2.tsx View File

@@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid"; import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2"; import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/dashboard/actions";
import { ModalFormInput } from "@/app/api/po/actions";
import { escape } from "lodash"; import { escape } from "lodash";


interface Props { interface Props {


+ 41
- 22
src/components/PoDetail/EscalationComponent.tsx View File

@@ -83,10 +83,18 @@ const EscalationComponent: React.FC<Props> = ({
return ( return (
// <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}>
<> <>
<Paper>
<Paper sx={{padding: 2}}>
{/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<FormControlLabel
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="body1">上報結果</Typography>
{/* {isCollapsed ? (
<ExpandLessIcon sx={{ ml: 1 }} />
) : (
<ExpandMoreIcon sx={{ ml: 1 }} />
)} */}
</Box>
{/* <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={isCollapsed} checked={isCollapsed}
@@ -104,23 +112,10 @@ const EscalationComponent: React.FC<Props> = ({
)} )}
</Box> </Box>
} }
/>
/> */}
</Box> </Box>
<Collapse in={isCollapsed}> <Collapse in={isCollapsed}>
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{forSupervisor ? (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
): undefined}
<FormControl fullWidth> <FormControl fullWidth>
<select <select
id="name" id="name"
@@ -136,7 +131,31 @@ const EscalationComponent: React.FC<Props> = ({
))} ))}
</select> </select>
</FormControl> </FormControl>
<TextField
{forSupervisor ? (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
): undefined}
{forSupervisor && (<TextField
fullWidth
id="decision"
name="decision"
label="上報結果"
type="radio"
// value={formData.decision}
onChange={handleInputChange}
InputProps={{ inputProps: { min: 1 } }}
placeholder="請決定上報結果"
/>)}
{/* <TextField
fullWidth fullWidth
id="quantity" id="quantity"
name="quantity" name="quantity"
@@ -146,21 +165,21 @@ const EscalationComponent: React.FC<Props> = ({
onChange={handleInputChange} onChange={handleInputChange}
InputProps={{ inputProps: { min: 1 } }} InputProps={{ inputProps: { min: 1 } }}
placeholder="請輸入數量" placeholder="請輸入數量"
/>
/> */}


<TextField <TextField
fullWidth fullWidth
id="message" id="message"
name="message" name="message"
label="備註"
label="上報原因"
multiline multiline
rows={4} rows={4}
value={formData.message} value={formData.message}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="請輸入您的備註"
placeholder="請輸入上報原因"
/> />


<Stack direction="row" justifyContent="flex-end" gap={1}>
{/* <Stack direction="row" justifyContent="flex-end" gap={1}>
<Button <Button
type="submit" type="submit"
variant="contained" variant="contained"
@@ -168,7 +187,7 @@ const EscalationComponent: React.FC<Props> = ({
> >
{t("update qc info")} {t("update qc info")}
</Button> </Button>
</Stack>
</Stack> */}
</Box> </Box>
</Collapse> </Collapse>
</Paper> </Paper>


+ 77
- 43
src/components/PoDetail/PoDetail.tsx View File

@@ -43,9 +43,11 @@ import {
import { import {
checkPolAndCompletePo, checkPolAndCompletePo,
fetchPoInClient, fetchPoInClient,
fetchPoListClient,
fetchStockInLineInfo, fetchStockInLineInfo,
PurchaseQcResult, PurchaseQcResult,
startPo, startPo,
createStockInLine
} from "@/app/api/po/actions"; } from "@/app/api/po/actions";
import { import {
useCallback, useCallback,
@@ -66,21 +68,20 @@ import PoQcStockInModal from "./PoQcStockInModal";
import QrModal from "./QrModal"; import QrModal from "./QrModal";
import { PlayArrow } from "@mui/icons-material"; import { PlayArrow } from "@mui/icons-material";
import DoneIcon from "@mui/icons-material/Done"; import DoneIcon from "@mui/icons-material/Done";
import { getCustomWidth } from "@/app/utils/commonUtil";
import { downloadFile, getCustomWidth } from "@/app/utils/commonUtil";
import PoInfoCard from "./PoInfoCard"; import PoInfoCard from "./PoInfoCard";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import { fetchPoListClient } from "@/app/api/po/actions";
import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material"; import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material";
import { createStockInLine } from "@/app/api/dashboard/actions";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers";
import { debounce } from "lodash"; import { debounce } from "lodash";
import LoadingComponent from "../General/LoadingComponent";
import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions";
//import { useRouter } from "next/navigation"; //import { useRouter } from "next/navigation";





type Props = { type Props = {
po: PoResult; po: PoResult;
qc: QcItemWithChecks[]; qc: QcItemWithChecks[];
@@ -115,7 +116,7 @@ const PoSearchList: React.FC<{
return ( return (
<Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}> <Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
{t("Purchase Orders")}
{t("Purchase Order")}
</Typography> </Typography>
<TextField <TextField
label={t("Search")} label={t("Search")}
@@ -133,44 +134,49 @@ const PoSearchList: React.FC<{
), ),
}} }}
/> />
<List dense sx={{ width: '100%' }}>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding sx={{ width: '100%' }}>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
width: '100%',
"&.Mui-selected": {
backgroundColor: "primary.light",
"&:hover": {
{(filteredPoList.length > 0)? (
<List dense sx={{ width: '100%' }}>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding sx={{ width: '100%' }}>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
width: '100%',
"&.Mui-selected": {
backgroundColor: "primary.light", backgroundColor: "primary.light",
"&:hover": {
backgroundColor: "primary.light",
},
}, },
},
}}
>
<ListItemText
primary={
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{poItem.code}
</Typography>
}
secondary={
<Typography variant="caption" color="text.secondary">
{t(`${poItem.status.toLowerCase()}`)}
</Typography>
}
/>
</ListItemButton>
</ListItem>
{index < filteredPoList.length - 1 && <Divider />}
</div>
))}
</List>
}}
>
<ListItemText
primary={
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{poItem.code}
</Typography>
}
secondary={
<Typography variant="caption" color="text.secondary">
{t(`${poItem.status.toLowerCase()}`)}
</Typography>
}
/>
</ListItemButton>
</ListItem>
{index < filteredPoList.length - 1 && <Divider />}
</div>
))}
</List>) : (
<LoadingComponent/>
)
}
{searchTerm && ( {searchTerm && (
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}> <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}>
{t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")}
{`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`}
{/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */}
</Typography> </Typography>
)} )}
</Paper> </Paper>
@@ -184,7 +190,7 @@ interface PolInputResult {


const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const cameras = useContext(CameraContext); const cameras = useContext(CameraContext);
console.log(cameras);
// console.log(cameras);
const { t } = useTranslation("purchaseOrder"); const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef(); const apiRef = useGridApiRef();
const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); const [purchaseOrder, setPurchaseOrder] = useState({ ...po });
@@ -205,6 +211,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null); const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null);
const defaultPolId = searchParams.get("polId")
useEffect(() => {
if (defaultPolId) {
setSelectedRow(rows.find((r) => r.id.toString() === defaultPolId) ?? null)
}
}, [])


const [stockInLine, setStockInLine] = useState<StockInLine[]>([]); const [stockInLine, setStockInLine] = useState<StockInLine[]>([]);
const [processedQty, setProcessedQty] = useState(0); const [processedQty, setProcessedQty] = useState(0);
@@ -212,6 +224,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const router = useRouter(); const router = useRouter();
const [poList, setPoList] = useState<PoResult[]>([]); const [poList, setPoList] = useState<PoResult[]>([]);
const [selectedPoId, setSelectedPoId] = useState(po.id); const [selectedPoId, setSelectedPoId] = useState(po.id);
const [focusField, setFocusField] = useState<HTMLInputElement>();

const currentPoId = searchParams.get('id'); const currentPoId = searchParams.get('id');
const selectedIdsParam = searchParams.get('selectedIds'); const selectedIdsParam = searchParams.get('selectedIds');
// const [selectedRowId, setSelectedRowId] = useState<number | null>(null); // const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
@@ -261,6 +275,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setProcessedQty(result.pol[0].processed); setProcessedQty(result.pol[0].processed);
} }
} }
// if (focusField) {console.log(focusField);focusField.focus();}
} }
} catch (error) { } catch (error) {
console.error("Failed to fetch PO detail:", error); console.error("Failed to fetch PO detail:", error);
@@ -319,6 +334,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setPurchaseOrder(newPo); setPurchaseOrder(newPo);
}, [purchaseOrder.id]); }, [purchaseOrder.id]);


const handleMailTemplateForStockInLine = useCallback(async (stockInLineId: number) => {
const response = await getMailTemplateForStockInLine(stockInLineId)
if (response) {
downloadFile(new Uint8Array(response.blobValue), response.filename);
}
}, [])

useEffect(() => { useEffect(() => {
setRows(purchaseOrder.pol || []); setRows(purchaseOrder.pol || []);
}, [purchaseOrder]); }, [purchaseOrder]);
@@ -388,6 +410,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setTimeout(async () => { setTimeout(async () => {
// post stock in line // post stock in line
const oldId = row.id; const oldId = row.id;
const acceptedQty = Number(polInputList[rowIndex].dnQty);
if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update

const postData = { const postData = {
dnNo: dnFormProps.watch("dnNo"), dnNo: dnFormProps.watch("dnNo"),
dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")),
@@ -396,7 +421,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
itemName: row.itemName, itemName: row.itemName,
purchaseOrderId: row.purchaseOrderId, purchaseOrderId: row.purchaseOrderId,
purchaseOrderLineId: row.id, purchaseOrderLineId: row.id,
acceptedQty: polInputList[rowIndex].dnQty || 0,
acceptedQty: acceptedQty,
productLotNo: polInputList[rowIndex].lotNo || '', productLotNo: polInputList[rowIndex].lotNo || '',
// acceptedQty: secondReceiveQty || 0, // acceptedQty: secondReceiveQty || 0,
// acceptedQty: row.acceptedQty, // acceptedQty: row.acceptedQty,
@@ -447,6 +472,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
} }
// setPolInputList(() => temp) // setPolInputList(() => temp)
}, 300), [rowIndex]); }, 300), [rowIndex]);

// const [focusField, setFocusField] = useState<HTMLInputElement>();

const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1)
return ( return (
<> <>
<TableRow <TableRow
@@ -477,7 +506,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
<TableCell align="right">{integerFormatter.format(row.qty)}</TableCell> <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell>
<TableCell align="right">{integerFormatter.format(processedQty)}</TableCell> <TableCell align="right">{integerFormatter.format(processedQty)}</TableCell>
<TableCell align="left">{row.uom?.code}</TableCell> <TableCell align="left">{row.uom?.code}</TableCell>
<TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell>
{/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> */}
<TableCell align="right">{decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio)}</TableCell>
<TableCell align="left">{row.stockUom.stockUomCode}</TableCell> <TableCell align="left">{row.stockUom.stockUomCode}</TableCell>
{/* <TableCell align="right"> {/* <TableCell align="right">
{decimalFormatter.format(totalWeight)} {weightUnit} {decimalFormatter.format(totalWeight)} {weightUnit}
@@ -495,6 +525,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
variant="outlined" variant="outlined"
defaultValue={polInputList[rowIndex]?.lotNo ?? ''} defaultValue={polInputList[rowIndex]?.lotNo ?? ''}
onChange={handleChange} onChange={handleChange}
// onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}}
/> />
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
@@ -520,7 +551,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
handleStart() handleStart()
} }
> >
提交
{t("submit")}
</Button> </Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -554,6 +585,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</> </>
); );
} }
// ROW END

const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => { (_e, newValue) => {
@@ -824,6 +857,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
itemDetail={selectedRow} itemDetail={selectedRow}
warehouse={warehouse} warehouse={warehouse}
fetchPoDetail={fetchPoDetail} fetchPoDetail={fetchPoDetail}
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine}
/> />
</Box> </Box>
</TableCell> </TableCell>


+ 8
- 1
src/components/PoDetail/PoDetailWrapper.tsx View File

@@ -10,6 +10,7 @@ import PoDetail from "./PoDetail";
import { QcItemWithChecks } from "@/app/api/qc"; import { QcItemWithChecks } from "@/app/api/qc";
import { fetchWarehouseList } from "@/app/api/warehouse"; import { fetchWarehouseList } from "@/app/api/warehouse";
import { fetchQcItemCheck } from "@/app/api/qc/actions"; import { fetchQcItemCheck } from "@/app/api/qc/actions";
import { fetchEscalationCombo } from "@/app/api/user";


interface SubComponents { interface SubComponents {
Loading: typeof PoDetailLoading; Loading: typeof PoDetailLoading;
@@ -20,10 +21,16 @@ type Props = {
}; };


const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => {
const [poWithStockInLine, warehouse, qc] = await Promise.all([
const [
poWithStockInLine,
warehouse,
qc,
escalationCombo
] = await Promise.all([
fetchPoWithStockInLines(id), fetchPoWithStockInLines(id),
fetchWarehouseList(), fetchWarehouseList(),
fetchQcItemCheck(), fetchQcItemCheck(),
fetchEscalationCombo(),
]); ]);
// const poWithStockInLine = await fetchPoWithStockInLines(id) // const poWithStockInLine = await fetchPoWithStockInLines(id)




+ 1
- 1
src/components/PoDetail/PoInfoCard.tsx View File

@@ -19,7 +19,7 @@ type Props = {
po: PoResult; po: PoResult;
}; };


const PoInfoCard: React.FC<Props> = async (
const PoInfoCard: React.FC<Props> = (
{ {
// id // id
po po


+ 25
- 16
src/components/PoDetail/PoInputGrid.tsx View File

@@ -74,6 +74,7 @@ interface Props {
stockInLine: StockInLine[]; stockInLine: StockInLine[];
warehouse: WarehouseResult[]; warehouse: WarehouseResult[];
fetchPoDetail: (poId: string) => void; fetchPoDetail: (poId: string) => void;
handleMailTemplateForStockInLine: (stockInLineId: number) => void;
} }


export type StockInLineEntryError = { export type StockInLineEntryError = {
@@ -112,7 +113,8 @@ function PoInputGrid({
itemDetail, itemDetail,
stockInLine, stockInLine,
warehouse, warehouse,
fetchPoDetail
fetchPoDetail,
handleMailTemplateForStockInLine
}: Props) { }: Props) {
console.log(itemDetail); console.log(itemDetail);
const { t } = useTranslation("purchaseOrder"); const { t } = useTranslation("purchaseOrder");
@@ -270,6 +272,7 @@ function PoInputGrid({


const [newOpen, setNewOpen] = useState(false); const [newOpen, setNewOpen] = useState(false);
const stockInLineId = searchParams.get("stockInLineId"); const stockInLineId = searchParams.get("stockInLineId");
const poLineId = searchParams.get("poLineId");
const closeNewModal = useCallback(() => { const closeNewModal = useCallback(() => {
const newParams = new URLSearchParams(searchParams.toString()); const newParams = new URLSearchParams(searchParams.toString());
newParams.delete("stockInLineId"); // Remove the parameter newParams.delete("stockInLineId"); // Remove the parameter
@@ -282,46 +285,51 @@ const closeNewModal = useCallback(() => {


// Open modal // Open modal
const openNewModal = useCallback(() => { const openNewModal = useCallback(() => {
setNewOpen(true);
setNewOpen(() => true);
}, []); }, []);


// Button handler to update the URL and open the modal // Button handler to update the URL and open the modal
const handleNewQC = useCallback( const handleNewQC = useCallback(
(id: GridRowId, params: any) => async () => {
(id: GridRowId, params: any) => async() => {
// console.log(id) // console.log(id)
// console.log(params) // console.log(params)
setBtnIsLoading(true);
// setBtnIsLoading(true);
setRowModesModel((prev) => ({ setRowModesModel((prev) => ({
...prev, ...prev,
[id]: { mode: GridRowModes.View }, [id]: { mode: GridRowModes.View },
})); }));


const qcResult = await fetchQcDefaultValue(id); const qcResult = await fetchQcDefaultValue(id);
setModalInfo({
setModalInfo(() => ({
...params.row, ...params.row,
qcResult: qcResult, qcResult: qcResult,
receivedQty: itemDetail.receivedQty, receivedQty: itemDetail.receivedQty,
});
}));


setTimeout(() => { setTimeout(() => {
const newParams = new URLSearchParams(searchParams.toString()); const newParams = new URLSearchParams(searchParams.toString());
newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates
router.replace(`${pathname}?${newParams.toString()}`); router.replace(`${pathname}?${newParams.toString()}`);
console.log("hello")
// console.log("hello")
openNewModal() openNewModal()
setBtnIsLoading(false);
// setBtnIsLoading(false);
}, 200); }, 200);
}, },
[fetchQcDefaultValue, openNewModal, pathname, router, searchParams] [fetchQcDefaultValue, openNewModal, pathname, router, searchParams]
); );

// Open modal if `stockInLineId` exists in the URL // Open modal if `stockInLineId` exists in the URL
const [firstCheckForSil, setFirstCheckForSil] = useState(false)
useEffect(() => { useEffect(() => {
if (stockInLineId) {
console.log("heeloo")
console.log(stockInLineId)
handleNewQC(stockInLineId, apiRef.current.getRow(stockInLineId));
if (stockInLineId && itemDetail && !firstCheckForSil) {
// console.log("heeloo")
// console.log(stockInLineId)
// console.log(apiRef.current.getRow(stockInLineId))
setFirstCheckForSil(true)
const fn = handleNewQC(stockInLineId, {row: apiRef.current.getRow(stockInLineId)});
fn();
} }
}, [stockInLineId, newOpen, handleNewQC, apiRef]);
}, [stockInLineId, poLineId, itemDetail]);
const handleEscalation = useCallback( const handleEscalation = useCallback(
(id: GridRowId, params: any) => () => { (id: GridRowId, params: any) => () => {
// setBtnIsLoading(true); // setBtnIsLoading(true);
@@ -580,7 +588,7 @@ const closeNewModal = useCallback(() => {
}} }}
onClick={handleNewQC(params.row.id, params)} onClick={handleNewQC(params.row.id, params)}
color="inherit" color="inherit"
key="edit"
key={`edit`}
/>, />,
<GridActionsCellItem <GridActionsCellItem
icon={<Button icon={<Button
@@ -589,7 +597,7 @@ const closeNewModal = useCallback(() => {
variant="contained" variant="contained"
color="primary" color="primary"
sx={{ width: '150px' }} sx={{ width: '150px' }}
// onClick={formProps.handleSubmit(onSubmitEmailSupplier)}
onClick={() => handleMailTemplateForStockInLine(params.row.id as number)}
> >
{t("email supplier")} {t("email supplier")}
</Button>} </Button>}
@@ -598,7 +606,7 @@ const closeNewModal = useCallback(() => {
// color: "primary.main", // color: "primary.main",
// marginRight: 1, // marginRight: 1,
}} }}
onClick={handleNewQC(params.row.id, params)}
// onClick={handleNewQC(params.row.id, params)}
color="inherit" color="inherit"
key="edit" key="edit"
/>, />,
@@ -908,6 +916,7 @@ const closeNewModal = useCallback(() => {
open={newOpen} open={newOpen}
onClose={closeNewModal} onClose={closeNewModal}
itemDetail={modalInfo} itemDetail={modalInfo}
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine}
/> />
</> </>
) )


+ 49
- 42
src/components/PoDetail/QcFormVer2.tsx View File

@@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid";
import { StockInLine } from "@/app/api/po"; import { StockInLine } from "@/app/api/po";
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; import { stockInLineStatusMap } from "@/app/utils/formatUtil";
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import axios from "@/app/(main)/axios/axiosInstance"; import axios from "@/app/(main)/axios/axiosInstance";
import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { NEXT_PUBLIC_API_URL } from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance"; import axiosInstance from "@/app/(main)/axios/axiosInstance";
import EscalationComponent from "./EscalationComponent"; import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid"; import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2"; import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/dashboard/actions";
import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/po/actions";
import { escape } from "lodash"; import { escape } from "lodash";
import { PanoramaSharp } from "@mui/icons-material"; import { PanoramaSharp } from "@mui/icons-material";


interface Props { interface Props {
itemDetail: StockInLine;
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] };
qc: QcItemWithChecks[]; qc: QcItemWithChecks[];
disabled: boolean; disabled: boolean;
qcItems: QcData[] qcItems: QcData[]
@@ -88,8 +88,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>();
const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory);
const [qcResult, setQcResult] = useState();
// const [qcResult, setQcResult] = useState();
const qcAccept = watch("qcAccept"); const qcAccept = watch("qcAccept");
const qcResult = watch("qcResult");
console.log(qcResult);
// const [qcAccept, setQcAccept] = useState(true); // const [qcAccept, setQcAccept] = useState(true);
// const [qcItems, setQcItems] = useState(dummyQCData) // const [qcItems, setQcItems] = useState(dummyQCData)


@@ -184,43 +186,45 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI


const qcColumns: GridColDef[] = [ const qcColumns: GridColDef[] = [
{ {
field: "qcItem",
field: "code",
headerName: t("qcItem"), headerName: t("qcItem"),
flex: 2, flex: 2,
renderCell: (params) => ( renderCell: (params) => (
<Box> <Box>
<b>{params.value}</b><br/> <b>{params.value}</b><br/>
{params.row.qcDescription}<br/>
{params.row.name}<br/>
</Box> </Box>
), ),
}, },
{ {
field: 'isPassed',
field: 'qcPassed',
headerName: t("qcResult"), headerName: t("qcResult"),
flex: 1.5, flex: 1.5,
renderCell: (params) => { renderCell: (params) => {
const currentValue = params.value;
const currentValue = params.row;
console.log(currentValue.row);
return ( return (
<FormControl> <FormControl>
<RadioGroup <RadioGroup
row row
aria-labelledby="demo-radio-buttons-group-label" aria-labelledby="demo-radio-buttons-group-label"
value={currentValue === undefined ? "" : (currentValue ? "true" : "false")}
value={currentValue.qcPassed === undefined ? "" : (currentValue.qcPassed ? "true" : "false")}
// value={currentValue.qcPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.qcPassed ? "true" : "false")}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
setQcItems((prev) => setQcItems((prev) =>
prev.map((r): QcData => (r.id === params.id ? { ...r, isPassed: value === "true" } : r))
prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
); );
}} }}
name={`isPassed-${params.id}`}
name={`qcPassed-${params.id}`}
> >
<FormControlLabel <FormControlLabel
value="true" value="true"
control={<Radio />} control={<Radio />}
label="合格" label="合格"
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
sx={{ sx={{
color: currentValue === true ? "green" : "inherit",
color: currentValue.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"} "& .Mui-checked": {color: "green"}
}} }}
/> />
@@ -228,9 +232,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
value="false" value="false"
control={<Radio />} control={<Radio />}
label="不合格" label="不合格"
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
sx={{ sx={{
color: currentValue === false ? "red" : "inherit",
color: currentValue.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"} "& .Mui-checked": {color: "red"}
}} }}
/> />
@@ -248,8 +252,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<TextField <TextField
type="number" type="number"
size="small" size="small"
value={!params.row.isPassed? (params.value ?? '') : '0'}
disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"}
value={!params.row.qcPassed? (params.value ?? '') : '0'}
disabled={params.row.qcPassed || disabled}
onChange={(e) => { onChange={(e) => {
const v = e.target.value; const v = e.target.value;
const next = v === '' ? undefined : Number(v); const next = v === '' ? undefined : Number(v);
@@ -257,6 +261,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
setQcItems((prev) => setQcItems((prev) =>
prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r))
); );
// setValue(`failQty`,failQty);
}} }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
@@ -274,7 +279,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<TextField <TextField
size="small" size="small"
value={params.value ?? ''} value={params.value ?? ''}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(e) => { onChange={(e) => {
const remarks = e.target.value; const remarks = e.target.value;
// const next = v === '' ? undefined : Number(v); // const next = v === '' ? undefined : Number(v);
@@ -283,6 +288,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
); );
}} }}
// {...register(`qcResult.${params.row.rowIndex}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}
@@ -293,11 +301,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
}, },
] ]


useEffect(() => {
console.log(itemDetail);
}, [itemDetail]);

// Set initial value for acceptQty // Set initial value for acceptQty
useEffect(() => { useEffect(() => {
if (itemDetail?.demandQty > 0) { //!== undefined) { if (itemDetail?.demandQty > 0) { //!== undefined) {
@@ -308,10 +311,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
}, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]);


// const [openCollapse, setOpenCollapse] = useState(false) // const [openCollapse, setOpenCollapse] = useState(false)
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);


const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => {
const isFailed = qcItems.some((qc) => !qc.isPassed)
const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => {
const isFailed = qcItems.some((qc) => !qc.qcPassed)
console.log(isFailed) console.log(isFailed)
if (isFailed) { if (isFailed) {
setIsCollapsed(true) setIsCollapsed(true)
@@ -327,10 +330,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI




useEffect(() => { useEffect(() => {
console.log(itemDetail);
console.log("ItemDetail in QC:", itemDetail);
}, [itemDetail]); }, [itemDetail]);



useEffect(() => { useEffect(() => {
// onFailedOpenCollapse(qcItems) // This function is no longer needed // onFailedOpenCollapse(qcItems) // This function is no longer needed
}, [qcItems]); // Removed onFailedOpenCollapse from dependency array }, [qcItems]); // Removed onFailedOpenCollapse from dependency array
@@ -366,19 +370,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
/> */} /> */}
<StyledDataGrid <StyledDataGrid
columns={qcColumns} columns={qcColumns}
rows={qcItems}
rows={disabled? qcResult:qcItems}
autoHeight autoHeight
/> />
</Grid> </Grid>
{!qcAccept && (
<Grid item xs={12}>
<EscalationComponent
forSupervisor={false}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Grid>)}
</> </>
)} )}
{tabIndex == 1 && ( {tabIndex == 1 && (
@@ -425,7 +420,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
field.onChange(value); field.onChange(value);
}} }}
> >
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"}
<FormControlLabel disabled={disabled}
value="true" control={<Radio />} label="接受" /> value="true" control={<Radio />} label="接受" />
<Box sx={{mr:2}}> <Box sx={{mr:2}}>
<TextField <TextField
@@ -434,7 +429,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
sx={{ width: '150px' }} sx={{ width: '150px' }}
value={qcAccept? accQty : 0 } value={qcAccept? accQty : 0 }
defaultValue={accQty} defaultValue={accQty}
disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"}
disabled={!qcAccept || disabled}
{...register("acceptQty", { {...register("acceptQty", {
required: "acceptQty required!", required: "acceptQty required!",
})} })}
@@ -442,15 +437,27 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
helperText={errors.acceptQty?.message} helperText={errors.acceptQty?.message}
/> />
</Box> </Box>
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"}
<FormControlLabel disabled={disabled}
value="false" control={<Radio />} value="false" control={<Radio />}
sx={{"& .Mui-checked": {color: "red"}}} sx={{"& .Mui-checked": {color: "red"}}}
label="不接受及上報" />
label="不接受" />
<FormControlLabel disabled={disabled}
value="false" control={<Radio />}
sx={{"& .Mui-checked": {color: "blue"}}}
label="上報品檢結果" />
</RadioGroup> </RadioGroup>
)} )}
/> />
</FormControl> </FormControl>
</Grid> </Grid>
{!qcAccept && (
<Grid item xs={12}>
<EscalationComponent
forSupervisor={false}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Grid>)}
{/* {qcAccept && <Grid item xs={12}> {/* {qcAccept && <Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}> <Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Escalation Result")} {t("Escalation Result")}


+ 53
- 55
src/components/PoDetail/QcStockInModalVer2.tsx View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { StockInLine } from "@/app/api/po"; import { StockInLine } from "@/app/api/po";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import { import {
Box, Box,
Button, Button,
@@ -19,10 +19,9 @@ import StockInForm from "./StockInForm";
import StockInFormVer2 from "./StockInFormVer2"; import StockInFormVer2 from "./StockInFormVer2";
import QcFormVer2 from "./QcFormVer2"; import QcFormVer2 from "./QcFormVer2";
import PutawayForm from "./PutawayForm"; import PutawayForm from "./PutawayForm";
import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate";
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate";
import { useGridApiRef } from "@mui/x-data-grid"; import { useGridApiRef } from "@mui/x-data-grid";
import {submitDialogWithWarning} from "../Swal/CustomAlerts"; import {submitDialogWithWarning} from "../Swal/CustomAlerts";
import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions";
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil";
import dayjs from "dayjs"; import dayjs from "dayjs";


@@ -36,7 +35,7 @@ const style = {
px: 5, px: 5,
pb: 10, pb: 10,
display: "block", display: "block",
width: { xs: "60%", sm: "60%", md: "60%" },
width: { xs: "90%", sm: "90%", md: "90%" },
// height: { xs: "60%", sm: "60%", md: "60%" }, // height: { xs: "60%", sm: "60%", md: "60%" },
}; };
interface CommonProps extends Omit<ModalProps, "children"> { interface CommonProps extends Omit<ModalProps, "children"> {
@@ -55,6 +54,7 @@ interface CommonProps extends Omit<ModalProps, "children"> {
qc?: QcItemWithChecks[]; qc?: QcItemWithChecks[];
warehouse?: any[]; warehouse?: any[];
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject";
handleMailTemplateForStockInLine: (stockInLineId: number) => void;
} }
interface Props extends CommonProps { interface Props extends CommonProps {
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] };
@@ -70,8 +70,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
setItemDetail, setItemDetail,
qc, qc,
warehouse, warehouse,
handleMailTemplateForStockInLine,
}) => { }) => {
console.log(warehouse);
const { const {
t, t,
i18n: { language }, i18n: { language },
@@ -113,6 +113,15 @@ const [qcItems, setQcItems] = useState(dummyQCData)
setOpenPutaway(isPutaway); setOpenPutaway(isPutaway);
}, [open]) }, [open])
const [viewOnly, setViewOnly] = useState(false);
useEffect(() => {
if (itemDetail && itemDetail.status) {
const isViewOnly = itemDetail.status.toLowerCase() == "completed" || itemDetail.status.toLowerCase() == "rejected"
setViewOnly(isViewOnly)
}
}, [itemDetail]);


const [openPutaway, setOpenPutaway] = useState(false); const [openPutaway, setOpenPutaway] = useState(false);
const onOpenPutaway = useCallback(() => { const onOpenPutaway = useCallback(() => {
@@ -155,21 +164,25 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// Get QC data from the shared form context // Get QC data from the shared form context
const qcAccept = data.qcAccept; const qcAccept = data.qcAccept;
const acceptQty = data.acceptQty as number; const acceptQty = data.acceptQty as number;
const qcResults = qcItems;
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
// Validate QC data // Validate QC data
const validationErrors : string[] = []; const validationErrors : string[] = [];
// Check if all QC items have results // Check if all QC items have results
const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined);
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined);
if (itemsWithoutResult.length > 0) { if (itemsWithoutResult.length > 0) {
validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`);
validationErrors.push(`${t("QC items without result")}`);
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
} }


// Check if failed items have failed quantity // Check if failed items have failed quantity
const failedItemsWithoutQty = qcItems.filter(item =>
item.isPassed === false && (!item.failQty || item.failQty <= 0)
const failedItemsWithoutQty = qcResults.filter(item =>
item.qcPassed === false && (!item.failQty || item.failQty <= 0)
); );
if (failedItemsWithoutQty.length > 0) { if (failedItemsWithoutQty.length > 0) {
validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`);
validationErrors.push(`${t("Failed items must have failed quantity")}`);
// validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`);
} }


// Check if QC accept decision is made // Check if QC accept decision is made
@@ -184,10 +197,16 @@ const [qcItems, setQcItems] = useState(dummyQCData)


// Check if dates are input // Check if dates are input
if (data.productionDate === undefined || data.productionDate == null) { if (data.productionDate === undefined || data.productionDate == null) {
validationErrors.push("Production Date cannot be null!");
validationErrors.push("請輸入生產日期!");
} }
if (data.expiryDate === undefined || data.expiryDate == null) { if (data.expiryDate === undefined || data.expiryDate == null) {
validationErrors.push("Expiry Date cannot be null!");
validationErrors.push("請輸入到期日!");
}
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept) {
validationErrors.push("有不合格檢查項目,無法收貨!");
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
// confirmButtonText: t("confirm putaway"), html: ""});
// return;
} }


if (validationErrors.length > 0) { if (validationErrors.length > 0) {
@@ -205,12 +224,12 @@ const [qcItems, setQcItems] = useState(dummyQCData)
qcAccept: qcAccept? qcAccept : false, qcAccept: qcAccept? qcAccept : false,
acceptQty: acceptQty? acceptQty : 0, acceptQty: acceptQty? acceptQty : 0,
qcResult: qcItems.map(item => ({
qcResult: qcResults.map(item => ({
qcItemId: item.id, qcItemId: item.id,
// qcItem: item.qcItem,
// code: item.code,
// qcDescription: item.qcDescription, // qcDescription: item.qcDescription,
isPassed: item.isPassed? item.isPassed : false,
failQty: (item.failQty && !item.isPassed) ? item.failQty : 0,
qcPassed: item.qcPassed? item.qcPassed : false,
failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0,
// failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0,
remarks: item.remarks || '' remarks: item.remarks || ''
})) }))
@@ -218,42 +237,21 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// const qcData = data; // const qcData = data;


console.log("QC Data for submission:", qcData); console.log("QC Data for submission:", qcData);

await postStockInLine(qcData);
if (!qcData.qcResult.every((qc) => qc.isPassed) && qcData.qcAccept) {
submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
confirmButtonText: t("confirm putaway"), html: ""});
return;
if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""});
onOpenPutaway();
} else {
closeHandler({}, "backdropClick");
} }
await postStockInLineWithQc(qcData);
// return;
return ;
}, },
[onOpenPutaway, qcItems], [onOpenPutaway, qcItems],
); );
const postStockInLineWithQc = useCallback(async (qcData: PurchaseQCInput) => {
const args = {
...qcData
// id: itemDetail.id,
// purchaseOrderId: itemDetail.purchaseOrderId,
// purchaseOrderLineId: itemDetail.purchaseOrderLineId,
// itemId: itemDetail.itemId,
// ...data,
// productionDate: productionDate,
// expiryDate: expiryDate,
// receiptDate: receiptDate,
} as ModalFormInput;

await postStockInLine(args);

if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""});
onOpenPutaway();
} else {
closeHandler({}, "backdropClick");
}
return ;
},[onOpenPutaway,closeHandler]);


const postStockInLine = useCallback(async (args: ModalFormInput) => { const postStockInLine = useCallback(async (args: ModalFormInput) => {
const submitData = { const submitData = {
@@ -329,8 +327,8 @@ const [qcItems, setQcItems] = useState(dummyQCData)
const acceptQty = formProps.watch("acceptedQty") const acceptQty = formProps.watch("acceptedQty")


const checkQcIsPassed = useCallback((qcItems: QcData[]) => {
const isPassed = qcItems.every((qc) => qc.isPassed);
const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => {
const isPassed = qcItems.every((qc) => qc.qcPassed);
console.log(isPassed) console.log(isPassed)
if (isPassed) { if (isPassed) {
formProps.setValue("passingQty", acceptQty) formProps.setValue("passingQty", acceptQty)
@@ -343,7 +341,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
useEffect(() => { useEffect(() => {
// maybe check if submitted before // maybe check if submitted before
console.log(qcItems) console.log(qcItems)
checkQcIsPassed(qcItems)
// checkQcIsPassed(qcItems)
}, [qcItems, checkQcIsPassed]) }, [qcItems, checkQcIsPassed])


return ( return (
@@ -368,7 +366,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<PutawayForm <PutawayForm
itemDetail={itemDetail} itemDetail={itemDetail}
warehouse={warehouse!} warehouse={warehouse!}
disabled={false}
disabled={viewOnly}
/> />
<Stack direction="row" justifyContent="flex-end" gap={1}> <Stack direction="row" justifyContent="flex-end" gap={1}>
<Button <Button
@@ -406,7 +404,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<StockInFormVer2 itemDetail={itemDetail} disabled={false} />
<StockInFormVer2 itemDetail={itemDetail} disabled={viewOnly} />
</Grid> </Grid>
</Grid> </Grid>
{/* <Stack direction="row" justifyContent="flex-end" gap={1}> {/* <Stack direction="row" justifyContent="flex-end" gap={1}>
@@ -428,13 +426,13 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<QcFormVer2 <QcFormVer2
qc={qc!} qc={qc!}
itemDetail={itemDetail} itemDetail={itemDetail}
disabled={false}
disabled={viewOnly}
qcItems={qcItems} qcItems={qcItems}
setQcItems={setQcItems} setQcItems={setQcItems}
/> />
</Grid> </Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}> <Stack direction="row" justifyContent="flex-end" gap={1}>
{itemDetail.status.toLowerCase() != "completed" && (<Button
{!viewOnly && (<Button
id="qcSubmit" id="qcSubmit"
type="button" type="button"
variant="contained" variant="contained"


+ 7
- 7
src/components/PoDetail/StockInFormVer2.tsx View File

@@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({
{...register("dnNo", { {...register("dnNo", {
// required: "productLotNo required!", // required: "productLotNo required!",
})} })}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
// error={Boolean(errors.productLotNo)} // error={Boolean(errors.productLotNo)}
// helperText={errors.productLotNo?.message} // helperText={errors.productLotNo?.message}
/> />
@@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({
{...register("productLotNo", { {...register("productLotNo", {
// required: "productLotNo required!", // required: "productLotNo required!",
})} })}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
error={Boolean(errors.productLotNo)} error={Boolean(errors.productLotNo)}
helperText={errors.productLotNo?.message} helperText={errors.productLotNo?.message}
/> />
@@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({
sx={{ width: "100%" }} sx={{ width: "100%" }}
label={t("productionDate")} label={t("productionDate")}
value={productionDate ? dayjs(productionDate) : undefined} value={productionDate ? dayjs(productionDate) : undefined}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(date) => { onChange={(date) => {
if (!date) return; if (!date) return;
setValue( setValue(
@@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({
sx={{ width: "100%" }} sx={{ width: "100%" }}
label={t("expiryDate")} label={t("expiryDate")}
value={expiryDate ? dayjs(expiryDate) : undefined} value={expiryDate ? dayjs(expiryDate) : undefined}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(date) => { onChange={(date) => {
console.log(date); console.log(date);
if (!date) return; if (!date) return;
@@ -311,10 +311,10 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField <TextField
label={t("uom")} label={t("uom")}
fullWidth fullWidth
{...register("uom", {
{...register("uom.code", {
required: "uom required!", required: "uom required!",
})} })}
value={uom.code}
// value={uom?.code}
disabled={true} disabled={true}
/> />
</Grid> </Grid>
@@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField <TextField
label={t("acceptedQty")} label={t("acceptedQty")}
fullWidth fullWidth
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
{...register("acceptedQty", { {...register("acceptedQty", {
required: "acceptedQty required!", required: "acceptedQty required!",
})} })}


+ 30
- 24
src/components/PoDetail/dummyQcTemplate.tsx View File

@@ -1,52 +1,58 @@
import { PutawayLine } from "@/app/api/po/actions" import { PutawayLine } from "@/app/api/po/actions"
import { QcData } from "@/app/api/qc"


export interface QcData {
id: number,
qcItem: string,
qcDescription: string,
isPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
}
// export interface QcData {
// qcItemId: number,
// qcItem: string,
// qcDescription: string,
// isPassed: boolean | undefined
// failQty: number | undefined
// remarks: string | undefined
// }


export const dummyQCData: QcData[] = [ export const dummyQCData: QcData[] = [
{ {
id: 1,
qcItem: "包裝",
id: 4,
code: "包裝",
qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格",
isPassed: undefined,
name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格",
qcPassed: undefined,
failQty: undefined, failQty: undefined,
remarks: undefined, remarks: undefined,
}, },
{ {
id: 2,
qcItem: "肉質",
id: 5,
code: "肉質",
qcDescription: "肉質鬆散,則不合格", qcDescription: "肉質鬆散,則不合格",
isPassed: undefined,
name: "肉質鬆散,則不合格",
qcPassed: undefined,
failQty: undefined, failQty: undefined,
remarks: undefined, remarks: undefined,
}, },
{ {
id: 3,
qcItem: "顔色",
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,",
isPassed: undefined,
id: 6,
code: "顔色",
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格",
name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格",
qcPassed: undefined,
failQty: undefined, failQty: undefined,
remarks: undefined, remarks: undefined,
}, },
{ {
id: 4,
qcItem: "狀態",
id: 7,
code: "狀態",
qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格",
isPassed: undefined,
name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格",
qcPassed: undefined,
failQty: undefined, failQty: undefined,
remarks: undefined, remarks: undefined,
}, },
{ {
id: 5,
qcItem: "異物",
id: 8,
code: "異物",
qcDescription: "有不屬於本食材的雜質,則不合格", qcDescription: "有不屬於本食材的雜質,則不合格",
isPassed: undefined,
name: "有不屬於本食材的雜質,則不合格",
qcPassed: undefined,
failQty: undefined, failQty: undefined,
remarks: undefined, remarks: undefined,
}, },


+ 1
- 1
src/i18n/zh/common.json View File

@@ -40,7 +40,7 @@
"non-consumables": "非消耗品", "non-consumables": "非消耗品",
"fg": "成品", "fg": "成品",
"sfg": "半成品", "sfg": "半成品",
"item": "品",
"item": "品",
"FG":"成品", "FG":"成品",
"FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情",
"View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌",


+ 45
- 38
src/i18n/zh/dashboard.json View File

@@ -1,39 +1,46 @@
{ {
"Dashboard": "資訊展示面板",
"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": "進度圖表",
"Purchase Order Code": "採購單號",
"Item Name": "貨品名稱",
"Escalation Level": "上報等級",
"Reason": "原因",
"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": "物料總數",

"stock in escalation list": "收貨已上報列表"
}
"Dashboard": "資訊展示面板",
"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": "進度圖表",
"Purchase Order Code": "採購單號",
"Item Name": "貨品名稱",
"Escalation Level": "上報等級",
"Reason": "原因",
"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": "物料總數",
"stock in escalation list": "收貨已上報列表",
"Responsible for handling colleagues": "負責處理同事",
"Completed QC Total": "品檢完成數量",
"QC Fail Count": "品檢不合格數量",
"DN Date": "送貨日期",
"Received Qty": "收貨數量",
"Escalation List": "上報列表",
"Purchase UoM": "計量單位",
"QC Completed Count": "品檢完成數量"
}

+ 2
- 2
src/i18n/zh/inventory.json View File

@@ -15,6 +15,6 @@
"Base UoM": "基本單位", "Base UoM": "基本單位",
"Lot No": "批號", "Lot No": "批號",
"Expiry Date": "到期日", "Expiry Date": "到期日",
"No items are selected yet.": "未選擇項目",
"Item selected": "已選擇項目"
"No items are selected yet.": "未選擇貨品",
"Item selected": "已選擇貨品"
} }

+ 134
- 146
src/i18n/zh/purchaseOrder.json View File

@@ -1,147 +1,135 @@
{ {
"Purchase Order": "採購訂單",
"Purchase Receipt": "處理採購來貨",
"Code": "編號",
"OrderDate": "下單日期",
"Order Date": "下單日期",
"Order Date To": "下單日期至",
"ETA": "預計到貨日期",
"ETA To": "預計到貨日期至",
"Details": "詳情",
"Supplier": "供應商",
"Status": "來貨狀態",
"Escalated": "上報狀態",
"NotEscalated": "無上報",

"Do you want to start?": "確定開始嗎?",
"Start": "開始",
"Start Success": "開始成功",
"Start Fail": "開始失敗",
"Start PO": "開始採購訂單",
"Do you want to complete?": "確定完成嗎?",
"Cancel": "取消",
"Complete": "完成",
"Complete Success": "完成成功",
"Complete Fail": "完成失敗",
"Complete PO": "完成採購訂單",
"General": "一般",
"Bind Storage": "綁定倉位",
"Po No.": "採購訂單編號",
"PO No.": "採購訂單編號",
"itemNo": "貨品編號",
"itemName": "貨品名稱",
"Item Detail": "貨品詳情",
"Item Code": "貨品編號",
"Item Name": "貨品名稱",
"Item Qty": "貨品數量",
"Item Accepted Qty": "貨品已收貨數量",
"Item Purchase UoM": "貨品計量單位",
"qty": "訂單數量",
"uom": "計量單位",
"Stock UoM": "庫存單位",
"Stock In Qty": "收貨數量",
"total weight": "總重量",
"weight unit": "重量單位",
"price": "訂單貨值",
"processed": "已上架數量",
"expiryDate": "到期日",
"acceptedQty": "是次來貨數量",
"putawayQty": "上架數量",
"acceptQty": "揀收數量",
"printQty": "列印數量",
"qcResult": "品檢結果",
"weight": "重量",
"start": "開始",
"qc": "質量控制",
"escalation": "上報",
"stock in": "入庫",
"putaway": "上架",
"delete": "刪除",
"Accept quantity must be greater than 0": "揀收數量不能少於1",
"QC items without result": "請完成品檢結果",
"Failed items must have failed quantity": "請輸入不合格數量",
"qty cannot be greater than remaining qty": "數量不能大於剩餘數量",
"acceptQty must not greater than": "揀收數量不能大於",
"Record pol": "記錄採購訂單",
"Add some entries!": "添加條目!",
"draft": "草稿",
"pending": "待處理",
"determine1": "上報1",
"determine2": "上報2",
"determine3": "上報3",
"receiving": "收貨中",
"received": "已檢收",
"completed": "已上架",
"rejected": "已拒絕及上報",
"status": "狀態",

"acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
"qc Check": "質量控制檢查",
"Please select QC": "請選擇質量控制",
"failQty": "失敗數量",
"select qc": "選擇質量控制",
"enter a failQty": "請輸入失敗數量",
"qty too big": "數量過大",
"sampleRate": "抽樣率",
"sampleWeight": "樣本重量",
"totalWeight": "總重量",

"Escalation": "上報",
"to be processed": "待處理",
"supervisor": "管理層",

"Stock In Detail": "入庫詳情",
"productLotNo": "貨品批號",
"receiptDate": "收貨日期",
"acceptedWeight": "接受重量",
"productionDate": "生產日期",

"reportQty": "上報數量",

"Default Warehouse": "預設倉庫",
"Select warehouse": "選擇倉庫",
"Putaway Detail": "上架詳情",
"LotNo": "批號",
"Po Code": "採購訂單編號",
"No Warehouse": "沒有倉庫",
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。",
"receivedQty": "已來貨數量",
"dnQty": "送貨單數量",

"Accept submit": "接受來貨",
"qc processing": "處理來貨及品檢",
"putaway processing": "處理來貨及上架",
"view stockin": "查看收貨及品檢",
"putaway processing": "處理來貨及上架",
"putawayBtn": "上架",
"dnNo": "送貨單編號",
"dnDate": "送貨單日期",

"submitStockIn": "更新來貨資料",
"QC Info": "品檢資料",
"Escalation History": "上報記錄",
"Escalation Info": "上報資料",
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"confirm qc result": "確定品檢結果",
"warehouse": "倉庫",
"qcItem": "品檢項目",
"passed": "接受",
"failed": "不接受",
"failedQty": "不合格數",
"remarks": "備註",

"Reject": "拒絕",
"submit": "提交",
"print": "列印",
"bind": "綁定"


}
"Purchase Order": "採購訂單",
"Purchase Receipt": "處理採購來貨",
"Code": "編號",
"OrderDate": "下單日期",
"Order Date": "下單日期",
"Order Date To": "下單日期至",
"ETA": "預計到貨日期",
"ETA To": "預計到貨日期至",
"Details": "詳情",
"Supplier": "供應商",
"Status": "來貨狀態",
"Escalated": "上報狀態",
"NotEscalated": "無上報",
"Do you want to start?": "確定開始嗎?",
"Start": "開始",
"Start Success": "開始成功",
"Start Fail": "開始失敗",
"Start PO": "開始採購訂單",
"Do you want to complete?": "確定完成嗎?",
"Cancel": "取消",
"Complete": "完成",
"Complete Success": "完成成功",
"Complete Fail": "完成失敗",
"Complete PO": "完成採購訂單",
"General": "一般",
"Bind Storage": "綁定倉位",
"Po No.": "採購訂單編號",
"PO No.": "採購訂單編號",
"itemNo": "貨品編號",
"itemName": "貨品名稱",
"Item Detail": "貨品詳情",
"Item Code": "貨品編號",
"Item Name": "貨品名稱",
"Item Qty": "貨品數量",
"Item": "貨品",
"Item Accepted Qty": "貨品已收貨數量",
"Item Purchase UoM": "貨品計量單位",
"qty": "訂單數量",
"uom": "計量單位",
"Stock UoM": "庫存單位",
"Stock In Qty": "收貨數量",
"total weight": "總重量",
"weight unit": "重量單位",
"price": "訂單貨值",
"processed": "已上架數量",
"expiryDate": "到期日",
"acceptedQty": "是次來貨數量",
"putawayQty": "上架數量",
"acceptQty": "揀收數量",
"printQty": "列印數量",
"qcResult": "品檢結果",
"weight": "重量",
"start": "開始",
"qc": "質量控制",
"escalation": "上報",
"stock in": "入庫",
"putaway": "上架",
"delete": "刪除",
"Accept quantity must be greater than 0": "揀收數量不能少於1",
"QC items without result": "請完成品檢結果",
"Failed items must have failed quantity": "請輸入不合格數量",
"qty cannot be greater than remaining qty": "數量不能大於剩餘數量",
"acceptQty must not greater than": "揀收數量不能大於",
"Record pol": "記錄採購訂單",
"Add some entries!": "添加條目!",
"draft": "草稿",
"pending": "待處理",
"determine1": "上報1",
"determine2": "上報2",
"determine3": "上報3",
"receiving": "收貨中",
"received": "已檢收",
"completed": "已上架",
"rejected": "已拒絕及上報",
"status": "狀態",
"acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
"qc Check": "質量控制檢查",
"Please select QC": "請選擇質量控制",
"failQty": "失敗數量",
"select qc": "選擇質量控制",
"enter a failQty": "請輸入失敗數量",
"qty too big": "數量過大",
"sampleRate": "抽樣率",
"sampleWeight": "樣本重量",
"totalWeight": "總重量",
"Escalation": "上報",
"to be processed": "待處理",
"supervisor": "管理層",
"Stock In Detail": "入庫詳情",
"productLotNo": "貨品批號",
"receiptDate": "收貨日期",
"acceptedWeight": "接受重量",
"productionDate": "生產日期",
"reportQty": "上報數量",
"Default Warehouse": "預設倉庫",
"Select warehouse": "選擇倉庫",
"Putaway Detail": "上架詳情",
"LotNo": "批號",
"Po Code": "採購訂單編號",
"No Warehouse": "沒有倉庫",
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。",
"receivedQty": "已來貨數量",
"dnQty": "送貨單數量",
"Accept submit": "接受來貨",
"qc processing": "處理來貨及品檢",
"putaway processing": "處理來貨及上架",
"view stockin": "查看收貨及品檢",
"putawayBtn": "上架",
"dnNo": "送貨單編號",
"dnDate": "送貨單日期",
"submitStockIn": "更新來貨資料",
"QC Info": "品檢資料",
"Escalation History": "上報記錄",
"Escalation Info": "上報資料",
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"confirm qc result": "確定品檢結果",
"warehouse": "倉庫",
"qcItem": "品檢項目",
"passed": "接受",
"failed": "不接受",
"failedQty": "不合格數",
"remarks": "備註",
"Reject": "拒絕",
"submit": "提交",
"print": "列印",
"bind": "綁定",
"Search": "搜尋",
"Found": "已找到"
}

+ 8
- 1
src/theme/devias-material-kit/components.ts View File

@@ -314,7 +314,7 @@ const components: ThemeOptions["components"] = {
styleOverrides: { styleOverrides: {
root: { root: {
borderBottomColor: palette.divider, borderBottomColor: palette.divider,
padding: "1px 6px",
padding: "10px 6px",
fontSize: defaultFontSize - 2, fontSize: defaultFontSize - 2,
// padding: "15px 16px", // padding: "15px 16px",
// lineHeight: 1.5, // lineHeight: 1.5,
@@ -342,6 +342,13 @@ const components: ThemeOptions["components"] = {
}, },
}, },
}, },
// MuiTableFooter: {
// styleOverrides: {
// root: {
// padding: "1px 6px",
// },
// },
// },
MuiTextField: { MuiTextField: {
defaultProps: { defaultProps: {
variant: "filled", variant: "filled",


Loading…
Cancel
Save