Ver código fonte

update

master
CANCERYS\kw093 3 meses atrás
pai
commit
c041fb35f6
13 arquivos alterados com 457 adições e 13 exclusões
  1. +38
    -0
      src/app/(main)/do/edit/page.tsx
  2. +1
    -1
      src/app/(main)/finishedGood/page.tsx
  3. +57
    -0
      src/app/api/do/actions.tsx
  4. +106
    -1
      src/components/DoDetail/DoDetail.tsx
  5. +26
    -0
      src/components/DoDetail/DoDetailWrapper.tsx
  6. +97
    -0
      src/components/DoDetail/DoInfoCard.tsx
  7. +98
    -0
      src/components/DoDetail/DoLineTable.tsx
  8. +3
    -0
      src/components/DoDetail/index.ts
  9. +16
    -0
      src/components/DoSearch/DoSearch.tsx
  10. +10
    -7
      src/components/FinishedGoodSearch/CombinedLotTable.tsx
  11. +1
    -1
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  12. +2
    -2
      src/components/PickOrderSearch/LotTable.tsx
  13. +2
    -1
      src/i18n/zh/pickOrder.json

+ 38
- 0
src/app/(main)/do/edit/page.tsx Ver arquivo

@@ -0,0 +1,38 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import DoDetail from "@/components/DoDetail/DodetailWrapper";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import { isArray } from "lodash";
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Edit Delivery Order Detail"
}

type Props = SearchParams;

const DoEdit: React.FC<Props> = async ({ searchParams }) => {
const { t } = await getServerI18n("do");
const id = searchParams["id"];

if (!id || isArray(id) || !isFinite(parseInt(id))) {
notFound();
}

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Edit Delivery Order Detail")}
</Typography>
<I18nProvider namespaces={["do", "common"]}>
<Suspense fallback={<DoDetail.Loading />}>
<DoDetail id={parseInt(id)} />
</Suspense>
</I18nProvider>
</>
);
}

export default DoEdit;

+ 1
- 1
src/app/(main)/finishedGood/page.tsx Ver arquivo

@@ -7,7 +7,7 @@ import { Metadata } from "next";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Pick Order",
title: "Finished Good Order",
};

const PickOrder: React.FC = async () => {


+ 57
- 0
src/app/api/do/actions.tsx Ver arquivo

@@ -12,3 +12,60 @@ import { GridRowId, GridRowSelectionModel } from "@mui/x-data-grid";
export interface CreateConsoDoInput {
ids: GridRowSelectionModel;
}
export interface DoDetail {
id: number;
code: string;
supplierCode: string;
shopCode: string;
currencyCode: string;
orderDate: string;
estimatedArrivalDate: string;
completeDate: string;
status: string;
deliveryOrderLines: DoDetailLine[];
}

export interface DoDetailLine {
id: number;
itemNo: string;
qty: number;
price: number;
status: string;
itemName?: string;
uomCode?: string;
}
export interface ReleaseDoRequest {
id: number;
}

export interface ReleaseDoResponse {
id: number;
entity: { status: string }
}
export const releaseDo = cache(async (data: ReleaseDoRequest) => {
return await serverFetchJson<ReleaseDoResponse>(`${BASE_API_URL}/do/release`,
{
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
})
export const preloadDo = () => {
fetchDoList();
};

export const fetchDoList = cache(async () => {
return serverFetchJson<DoResult[]>(`${BASE_API_URL}/do/list`, {
next: { tags: ["doList"] },
});
});

export const fetchDoDetail = cache(async (id: number) => {
return serverFetchJson<DoDetail>(`${BASE_API_URL}/do/detail/${id}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["doDetail"]
}
});
});

+ 106
- 1
src/components/DoDetail/DoDetail.tsx Ver arquivo

@@ -1,3 +1,108 @@
"use client";

// const doDetail =
import type { DoDetail as DoDetailType } from "@/app/api/do/actions";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import useUploadContext from "../UploadProvider/useUploadContext";
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
import { useCallback, useState } from "react";
import { Button, Stack, Typography } from "@mui/material";
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import StartIcon from "@mui/icons-material/Start";
import { releaseDo } from "@/app/api/do/actions";
import DoInfoCard from "./DoInfoCard";
import DoLineTable from "./DoLineTable";

type Props = {
id?: number;
defaultValues: Partial<DoDetailType> | undefined;
}

const DoDetail: React.FC<Props> = ({
defaultValues,
id,
}) => {
const { t } = useTranslation("do")
const router = useRouter();
const { setIsUploading } = useUploadContext();
const [serverError, setServerError] = useState("");

const formProps = useForm<DoDetailType>({
defaultValues: defaultValues
})

const handleBack = useCallback(() => {
router.replace(`/do`)
}, [])

const handleRelease = useCallback(async () => {
try {
setIsUploading(true)
if (id) {
console.log(id)
const response = await releaseDo({ id: id })
console.log(response.entity.status)
if (response) {
formProps.setValue("status", response.entity.status)
console.log(formProps.watch("status"))
}
}

} catch (e) {
// backend error
setServerError(t("An error has occurred. Please try again later."));
console.log(e);
} finally {
setIsUploading(false)
}
}, [id, formProps, t, setIsUploading])

const onSubmit = useCallback<SubmitHandler<DoDetailType>>(async (data, event) => {
console.log(data)
}, [t])

const onSubmitError = useCallback<SubmitErrorHandler<DoDetailType>>((errors) => {
console.log(errors)
}, [t])

return <>
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
</Typography>
)}
{
formProps.watch("status")?.toLowerCase() === "pending" && (
<Stack direction="row" justifyContent="flex-start" gap={1}>
<Button
variant="outlined"
startIcon={<StartIcon />}
onClick={handleRelease}
>
{t("Release")}
</Button>
</Stack>
)}
<DoInfoCard />
<DoLineTable />
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={handleBack}
>
{t("Back")}
</Button>
</Stack>
</Stack>
</FormProvider>
</>
}

export default DoDetail;

+ 26
- 0
src/components/DoDetail/DoDetailWrapper.tsx Ver arquivo

@@ -0,0 +1,26 @@
import React from "react";
import GeneralLoading from "../General/GeneralLoading";
import { fetchDoDetail } from "@/app/api/do/actions";
import DoDetail from "./DoDetail";

interface SubComponents {
Loading: typeof GeneralLoading;
}

type DoDetailProps = {
id?: number;
}

type Props = DoDetailProps

const DoDetailWrapper: React.FC<Props> & SubComponents = async ({
id,
}) => {
const doDetail = id ? await fetchDoDetail(id) : undefined

return <DoDetail id={id} defaultValues={doDetail}/>
}

DoDetailWrapper.Loading = GeneralLoading;

export default DoDetailWrapper;

+ 97
- 0
src/components/DoDetail/DoInfoCard.tsx Ver arquivo

@@ -0,0 +1,97 @@
import { DoDetail } from "@/app/api/do/actions";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import { Box, Card, CardContent, Grid, Stack, TextField } from "@mui/material";
import { upperFirst } from "lodash";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";

type Props = {

};

const DoInfoCard: React.FC<Props> = ({

}) => {
const { t } = useTranslation("do");

const { control, getValues, register, watch } = useFormContext<DoDetail>();

return (
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Status")}
fullWidth
disabled={true}
value={`${t(upperFirst(watch("status")))}`}
/>
</Grid>
<Grid item xs={6}/>
<Grid item xs={6}>
<TextField
{...register("code")}
label={t("Code")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("supplierCode")}
label={t("Supplier Code")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("shopCode")}
label={t("Shop Code")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("currencyCode")}
label={t("Currency Code")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("orderDate")}
label={t("Order Date")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("estimatedArrivalDate")}
label={t("Estimated Arrival Date")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("completeDate")}
label={t("Complete Date")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}/>
</Grid>
</Box>
</CardContent>
</Card>
)
}

export default DoInfoCard;

+ 98
- 0
src/components/DoDetail/DoLineTable.tsx Ver arquivo

@@ -0,0 +1,98 @@
import { DoDetail } from "@/app/api/do/actions";
import { decimalFormatter } from "@/app/utils/formatUtil";
import { GridColDef } from "@mui/x-data-grid";
import { isEmpty, upperFirst } from "lodash";
import { useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid/StyledDataGrid";

type Props = {

};

const DoLineTable: React.FC<Props> = ({

}) => {
const { t } = useTranslation("do")
const {
watch
} = useFormContext<DoDetail>()

const columns = useMemo<GridColDef[]>(() => [
{
field: "itemNo",
headerName: t("Item No."),
flex: 1,
},
{
field: "itemName",
headerName: t("Item Name"),
flex: 1,
renderCell: (row) => {
return isEmpty(row.value) ? "N/A" : row.value
},
},
{
field: "qty",
headerName: t("Quantity"),
flex: 1,
align: "right",
headerAlign: "right",
renderCell: (row) => {
return decimalFormatter.format(row.value)
},
},
{
field: "price",
headerName: t("Price"),
flex: 1,
align: "right",
headerAlign: "right",
renderCell: (row) => {
return decimalFormatter.format(row.value)
},
},
{
field: "uomCode",
headerName: t("UoM"),
flex: 1,
align: "left",
headerAlign: "left",
renderCell: (row) => {
return isEmpty(row.value) ? "N/A" : row.value
},
},
{
field: "status",
headerName: t("Status"),
flex: 1,
renderCell: (row) => {
return t(upperFirst(row.value))
},
},
], [t])

return (
<>
<StyledDataGrid
sx={{
"--DataGrid-overlayHeight": "100px",
".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
border: "1px solid",
borderColor: "error.main",
},
".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
border: "1px solid",
borderColor: "warning.main",
},
}}
disableColumnMenu
rows={watch("deliveryOrderLines")}
columns={columns}
/>
</>
)
}

export default DoLineTable;

+ 3
- 0
src/components/DoDetail/index.ts Ver arquivo

@@ -0,0 +1,3 @@
export { default } from "./DodetailWrapper";
export { default as DoInfoCard } from './DoInfoCard';
export { default as DoLineTable } from './DoLineTable';

+ 16
- 0
src/components/DoSearch/DoSearch.tsx Ver arquivo

@@ -155,6 +155,22 @@ const DoSearch: React.FC<Props> = ({ dos }) => {
// onClick: onDetailClick,
// buttonIcon: <EditNote />,
// },

{
field: "id",
headerName: t("Details"),
width: 100,
renderCell: (params) => (
<Button
variant="outlined"
size="small"
startIcon={<EditNote />}
onClick={() => onDetailClick(params.row)}
>
{t("Details")}
</Button>
),
},
{
field: "code",
headerName: t("code"),


+ 10
- 7
src/components/FinishedGoodSearch/CombinedLotTable.tsx Ver arquivo

@@ -85,13 +85,14 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
<TableCell>{t("Item Code")}</TableCell>
<TableCell>{t("Item Name")}</TableCell>
<TableCell>{t("Lot No")}</TableCell>
<TableCell>{t("Expiry Date")}</TableCell>
{/* <TableCell>{t("Expiry Date")}</TableCell> */}
<TableCell>{t("Location")}</TableCell>
<TableCell align="right">{t("Current Stock")}</TableCell>
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell align="right">{t("Qty Already Picked")}</TableCell>
<TableCell align="right">{t("Lot Actual Pick Qty")}</TableCell>
<TableCell>{t("Stock Unit")}</TableCell>
<TableCell align="right">{t("Available Qty")}</TableCell>
<TableCell align="right">{t("Required Qty")}</TableCell>
<TableCell align="right">{t("Actual Pick Qty")}</TableCell>
<TableCell align="right">{t("Pick Qty")}</TableCell>
<TableCell align="center">{t("Submit")}</TableCell>
<TableCell align="center">{t("Reject")}</TableCell>
</TableRow>
@@ -128,11 +129,12 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
<TableCell sx={{ color: textColor }}>{lot.itemCode}</TableCell>
<TableCell sx={{ color: textColor }}>{lot.itemName}</TableCell>
<TableCell sx={{ color: textColor }}>{lot.lotNo}</TableCell>
<TableCell sx={{ color: textColor }}>
{/* <TableCell sx={{ color: textColor }}>
{lot.expiryDate ? new Date(lot.expiryDate).toLocaleDateString() : 'N/A'}
</TableCell>
*/}
<TableCell sx={{ color: textColor }}>{lot.location}</TableCell>
<TableCell sx={{ color: textColor }}>{lot.stockUnit}</TableCell>
<TableCell align="right" sx={{ color: textColor }}>{lot.availableQty}</TableCell>
<TableCell align="right" sx={{ color: textColor }}>{lot.requiredQty}</TableCell>
<TableCell align="right" sx={{ color: textColor }}>{lot.actualPickQty || 0}</TableCell>
@@ -165,6 +167,7 @@ const CombinedLotTable: React.FC<CombinedLotTableProps> = ({
}}
/>
</TableCell>
<TableCell sx={{ color: textColor }}>{lot.stockUnit}</TableCell>
<TableCell align="center">
<Button
variant="contained"


+ 1
- 1
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx Ver arquivo

@@ -245,7 +245,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
<Grid container>
<Grid item xs={8}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Pick Order")}
{t("Finished Good Order")}
</Typography>
</Grid>
{/*


+ 2
- 2
src/components/PickOrderSearch/LotTable.tsx Ver arquivo

@@ -462,8 +462,8 @@ const LotTable: React.FC<LotTableProps> = ({
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell>{t("Stock Unit")}</TableCell>
<TableCell align="center">{t("QR Code Scan")}</TableCell>
<TableCell align="center">{t("QC Check")}</TableCell>
<TableCell align="right">{t("Lot Actual Pick Qty")}</TableCell>
<TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell>
<TableCell align="right">{t("Reject")}</TableCell>
<TableCell align="center">{t("Submit")}</TableCell>
</TableRow>
</TableHead>


+ 2
- 1
src/i18n/zh/pickOrder.json Ver arquivo

@@ -188,5 +188,6 @@
"Expiry Date": "到期日",
"Location": "位置",
"All Pick Order Lots": "所有提料單批次",
"Completed": "已完成"
"Completed": "已完成",
"Finished Good Order": "成品訂單"
}

Carregando…
Cancelar
Salvar