浏览代码

update

master
MSI\derek 2 个月前
父节点
当前提交
4c7c12d76c
共有 20 个文件被更改,包括 1293 次插入182 次删除
  1. +30
    -0
      src/app/(main)/pickOrder/detail/page.tsx
  2. +1
    -1
      src/app/(main)/pickOrder/page.tsx
  3. +84
    -0
      src/app/api/pickOrder/actions.ts
  4. +64
    -4
      src/app/api/pickOrder/index.ts
  5. +7
    -5
      src/app/api/qrcode/index.ts
  6. +5
    -0
      src/app/utils/fetchUtil.ts
  7. +1
    -0
      src/components/Breadcrumb/Breadcrumb.tsx
  8. +1
    -1
      src/components/NavigationContent/NavigationContent.tsx
  9. +276
    -0
      src/components/PickOrderDetail/PickOrderDetail.tsx
  10. +40
    -0
      src/components/PickOrderDetail/PickOrderDetailLoading.tsx
  11. +35
    -0
      src/components/PickOrderDetail/PickOrderDetailWrapper.tsx
  12. +1
    -0
      src/components/PickOrderDetail/index.ts
  13. +91
    -0
      src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx
  14. +115
    -0
      src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx
  15. +241
    -6
      src/components/PickOrderSearch/ConsolidatedPickOrders.tsx
  16. +152
    -81
      src/components/PickOrderSearch/PickOrderSearch.tsx
  17. +9
    -2
      src/components/PickOrderSearch/PickOrderSearchWrapper.tsx
  18. +138
    -81
      src/components/PickOrderSearch/PickOrders.tsx
  19. +1
    -1
      src/components/PoSearch/PoSearch.tsx
  20. +1
    -0
      src/i18n/zh/common.json

+ 30
- 0
src/app/(main)/pickOrder/detail/page.tsx 查看文件

@@ -0,0 +1,30 @@
import { PreloadPickOrder } from "@/app/api/pickorder";
import { SearchParams } from "@/app/utils/fetchUtil";
import PickOrderDetail from "@/components/PickOrderDetail";
import { getServerI18n, I18nProvider } from "@/i18n";
import { Stack, Typography } from "@mui/material";
import { Metadata } from "next";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Consolidated Pick Order Flow",
};
type Props = {} & SearchParams;

const PickOrder: React.FC<Props> = async ({ searchParams }) => {
const { t } = await getServerI18n("pickOrder");

PreloadPickOrder();

return (
<>
<I18nProvider namespaces={["pickOrder"]}>
<Suspense fallback={<PickOrderDetail.Loading />}>
<PickOrderDetail consoCode={`${searchParams["consoCode"]}`}/>
</Suspense>
</I18nProvider>
</>
);
};

export default PickOrder;

+ 1
- 1
src/app/(main)/pickOrder/page.tsx 查看文件

@@ -1,4 +1,4 @@
import { PreloadPickOrder } from "@/app/api/pickOrder";
import { PreloadPickOrder } from "@/app/api/pickorder";
import PickOrderSearch from "@/components/PickOrderSearch";
import { getServerI18n } from "@/i18n";
import { Stack, Typography } from "@mui/material";


+ 84
- 0
src/app/api/pickOrder/actions.ts 查看文件

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


export const consolidatePickOrder = async (ids: number[]) => {
const pickOrder = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/conso`, {
method: "POST",
body: JSON.stringify({ ids: ids }),
headers: { "Content-Type": "application/json" },
});
// revalidateTag("po");
return pickOrder
}

export const consolidatePickOrder_revert = async (ids: number[]) => {
const pickOrder = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/deconso`, {
method: "POST",
body: JSON.stringify({ ids: ids }),
headers: { "Content-Type": "application/json" },
});
// revalidateTag("po");
return pickOrder
}


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

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

export const fetchConsoPickOrderLineClient = cache(async (queryParams?: Record<string, any>) => {
if (queryParams) {
const queryString = new URLSearchParams(queryParams).toString();
return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage?${queryString}`, {
method: 'GET',
next: { tags: ["pickorder"] },
});
} else {
return serverFetchJson<RecordsRes<PickOrderLineWithSuggestedLot[]>>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage`, {
method: 'GET',
next: { tags: ["pickorder"] },
});
}
});

export const fetchConsoDetail = cache(async (consoCode: string) => {
return serverFetchJson<PreReleasePickOrderSummary>(`${BASE_API_URL}/pickOrder/releaseConso/${consoCode}`, {
method: 'GET',
next: { tags: ["pickorder"] },
});
});

+ 64
- 4
src/app/api/pickOrder/index.ts 查看文件

@@ -1,5 +1,5 @@
import "server-only";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

@@ -18,14 +18,74 @@ export interface PickOrderResult{
status: string,
releasedBy: string,
items?: PickOrderItemInfo[] | null,
pickOrderLine?: PickOrderLine[]
}

export interface PickOrderLine {
id: number,
itemId: number,
itemCode: string,
itemName: string,
availableQty: number,
requiredQty: number,
uomCode: string,
uomDesc: string
}
export interface ConsoPickOrderResult{
id: number,
code: string,
consoCode?: string,
targetDate: number[],
completeDate?: number[],
type: string,
status: string,
releasedBy: string,
items?: PickOrderItemInfo[] | null,
}

export interface FetchPickOrders extends Pageable {
code: string | undefined
targetDateFrom: string | undefined
targetDateTo: string | undefined
type: string | undefined
status: string | undefined
itemName: string | undefined
}
export type ByItemsSummary = {
id: number,
code: string,
name: string,
uomDesc: string,
availableQty: number,
requiredQty: number,
}
export interface PreReleasePickOrderSummary {
consoCode: string
pickOrders: Omit<PickOrderResult, "items">[]
items: ByItemsSummary[]
}

export interface PickOrderLineWithSuggestedLot {
itemName: string,
qty: number,
status: string
suggestedLotNo: string
}

export const PreloadPickOrder = () => {
fetchPickOrders()
fetchPickOrders({
code: undefined,
targetDateFrom: undefined,
targetDateTo: undefined,
type: undefined,
status: undefined,
itemName: undefined,
})
}

export const fetchPickOrders = cache(async () => {
return serverFetchJson<PickOrderResult[]>(`${BASE_API_URL}/pickOrder/list`, {
export const fetchPickOrders = cache(async (queryParams: FetchPickOrders) => {
const queryString = new URLSearchParams(queryParams as Record<string, any>).toString();
return serverFetchJson<PickOrderResult[]>(`${BASE_API_URL}/pickOrder/list?${queryString}`, {
next: {
tags: ["pickOrders"]
}


+ 7
- 5
src/app/api/qrcode/index.ts 查看文件

@@ -4,8 +4,10 @@ import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";

export interface QrCodeInfo {
stockInLineId?: number;
itemId: number
warehouseId?: number
lotNo?: string
}
// warehouse qrcode
warehouseId?: number
// item qrcode
stockInLineId?: number;
itemId: number
lotNo?: string
}

+ 5
- 0
src/app/utils/fetchUtil.ts 查看文件

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

export interface Pageable {
pageSize?: number
pageNum?: number
}

export type SearchParams = {
searchParams: { [key: string]: string | string[] | undefined };
}


+ 1
- 0
src/components/Breadcrumb/Breadcrumb.tsx 查看文件

@@ -24,6 +24,7 @@ const pathToLabelMap: { [path: string]: string } = {
"/do": "Delivery Order",
"/pickOrder": "Pick Order",
"/po": "Purchase Order",
"/dashboard": "dashboard",
};

const Breadcrumb = () => {


+ 1
- 1
src/components/NavigationContent/NavigationContent.tsx 查看文件

@@ -52,7 +52,7 @@ const NavigationContent: React.FC = () => {
{
icon: <RequestQuote />,
label: "Pick Order",
path: "/pickOrder",
path: "/pickorder",
},
// {
// icon: <RequestQuote />,


+ 276
- 0
src/components/PickOrderDetail/PickOrderDetail.tsx 查看文件

@@ -0,0 +1,276 @@
"use client";

import {
Button,
ButtonProps,
Card,
CardContent,
CardHeader,
CircularProgress,
Grid,
Stack,
Typography,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GridColDef } from "@mui/x-data-grid";
import { PlayArrow } from "@mui/icons-material";
import DoneIcon from "@mui/icons-material/Done";
import { GridRowSelectionModel } from "@mui/x-data-grid";
import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import { fetchConsoPickOrderLineClient } from "@/app/api/pickorder/actions";
import { PickOrderLineWithSuggestedLot } from "@/app/api/pickorder";
import { Pageable } from "@/app/utils/fetchUtil";

interface Props {
consoCode: string;
}
interface IsLoadingModel {
pickOrderLineTable: boolean;
stockOutLineTable: boolean;
}
const PickOrderDetail: React.FC<Props> = ({ consoCode }) => {
const { t } = useTranslation("pickOrder");
const [selectedRow, setSelectRow] = useState<GridRowSelectionModel>();
const [isLoadingModel, setIsLoadingModel] = useState<IsLoadingModel>({
pickOrderLineTable: false,
stockOutLineTable: false,
});
const [criteriaArgs, setCriteriaArgs] = useState<Pageable>({
pageNum: 1,
pageSize: 10,
});
const [polTotalCount, setPolTotalCount] = useState(0);
const [solTotalCount, setSolTotalCount] = useState(0);

const [suggestedList, setSuggestedList] = useState<
PickOrderLineWithSuggestedLot[]
>([]);

const sugggestedLotColumn = useMemo<GridColDef[]>(
() => [
{
field: "id",
headerName: "pickOrderLineId",
flex: 1,
},
{
field: "itemName",
headerName: "itemId",
flex: 1,
},
{
field: "qty",
headerName: "qty",
flex: 1,
},
{
field: "uom",
headerName: "uom",
flex: 1,
},
{
field: "suggestedLotNo",
headerName: "suggestedLotNo",
flex: 1,
},
],
[]
);
const [actualList, setActualList] = useState([]);
const actualLotColumn = useMemo<GridColDef[]>(
() => [
{
field: "code",
headerName: "actual lot (out line",
flex: 1,
},
],
[]
);

const handleStartPickOrder = useCallback(async () => {}, []);

const handleCompletePickOrder = useCallback(async () => {}, []);

const fetchSuggestedLotList = useCallback(
async (consoCode: string) => {},
[]
);

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

const buttonData = useMemo(() => {
switch ("purchaseOrder.status".toLowerCase()) {
case "pending":
return {
buttonName: "start",
title: t("Do you want to start?"),
confirmButtonText: t("Start"),
successTitle: t("Start Success"),
errorTitle: t("Start Fail"),
buttonText: t("Start PO"),
buttonIcon: <PlayArrow />,
buttonColor: "success",
disabled: false,
onClick: handleStartPickOrder,
};
case "receiving":
return {
buttonName: "complete",
title: t("Do you want to complete?"),
confirmButtonText: t("Complete"),
successTitle: t("Complete Success"),
errorTitle: t("Complete Fail"),
buttonText: t("Complete PO"),
buttonIcon: <DoneIcon />,
buttonColor: "info",
disabled: false,
onClick: handleCompletePickOrder,
};
default:
return {
buttonName: "complete",
title: t("Do you want to complete?"),
confirmButtonText: t("Complete"),
successTitle: t("Complete Success"),
errorTitle: t("Complete Fail"),
buttonText: t("Complete PO"),
buttonIcon: <DoneIcon />,
buttonColor: "info",
disabled: true,
};
// break;
}
}, [handleStartPickOrder, handleCompletePickOrder]);

const [isOpenScanner, setOpenScanner] = useState(false);
const onOpenScanner = useCallback(() => {
setOpenScanner(true);
}, []);

const onCloseScanner = useCallback(() => {
setOpenScanner(false);
}, []);

const fetchConsoPickOrderLine = useCallback(
async (params: Record<string, any>) => {
setIsLoadingModel((prev) => ({
...prev,
pickOrderLineTable: true,
}));

const res = await fetchConsoPickOrderLineClient({
...params,
consoCode: consoCode,
});
if (res) {
console.log(res);
setSuggestedList(res.records);
setPolTotalCount(res.total);
} else {
console.log("error");
console.log(res);
}
setIsLoadingModel((prev) => ({
...prev,
pickOrderLineTable: false,
}));
},
[fetchConsoPickOrderLineClient, consoCode]
);

useEffect(() => {
fetchConsoPickOrderLine(criteriaArgs);
}, [criteriaArgs]);

const scanner = useQcCodeScanner();
useEffect(() => {
if (isOpenScanner && !scanner.isScanning) {
scanner.startScan();
} else if (!isOpenScanner && scanner.isScanning) {
scanner.stopScan();
}
}, [isOpenScanner]);

// useEffect(() => {
// if (scanner.values.length > 0 && !Boolean(itemDetail)) {
// console.log(scanner.values[0]);
// const data: QrCodeInfo = JSON.parse(scanner.values[0]);
// console.log(data);
// if (data.stockInLineId) {
// console.log("still got in");
// console.log(data.stockInLineId);
// setStockInLineId(data.stockInLineId);
// }
// scanner.resetScan();
// }
// }, [scanner.values]);

return (
<>
<Stack spacing={2}>
<Grid container xs={12} justifyContent="start">
<Grid item xs={12}>
<Typography variant="h4" marginInlineEnd={2}>
{consoCode}
</Typography>
</Grid>
<Grid item xs={8}>
<Button
onClick={buttonData.onClick}
disabled={buttonData.disabled}
color={buttonData.buttonColor as ButtonProps["color"]}
startIcon={buttonData.buttonIcon}
>
{buttonData.buttonText}
</Button>
</Grid>
<Grid
item
xs={4}
display="flex"
justifyContent="end"
alignItems="end"
>
<Button onClick={onOpenScanner}>{t("bind")}</Button>
</Grid>
</Grid>
<Grid container xs={12} justifyContent="space-between">
{/* <Grid item xs={12} sx={{ height: 400 }}>
<StyledDataGrid rows={suggestedList} columns={columns} />
</Grid> */}
<Grid item xs={12} sx={{ height: 400 }}>
{isLoadingModel.pickOrderLineTable ? (
<CircularProgress size={40} />
) : (
<StyledDataGrid
rows={suggestedList}
columns={sugggestedLotColumn}
rowSelectionModel={selectedRow}
onRowSelectionModelChange={(newRowSelectionModel) => {
setSelectRow(newRowSelectionModel);
}}
pageSizeOptions={[2, 10, 25, 50, 100]}
onPaginationModelChange={async (model, details) => {
setCriteriaArgs({
pageNum: model.page + 1,
pageSize: model.pageSize,
});
}}
rowCount={polTotalCount}
/>
)}
</Grid>
<Grid item xs={12} sx={{ height: 400 }}>
<StyledDataGrid rows={actualList} columns={actualLotColumn} />
</Grid>
</Grid>
</Stack>
</>
);
};
export default PickOrderDetail;

+ 40
- 0
src/components/PickOrderDetail/PickOrderDetailLoading.tsx 查看文件

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const PickOrderDetailLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default PickOrderDetailLoading;

+ 35
- 0
src/components/PickOrderDetail/PickOrderDetailWrapper.tsx 查看文件

@@ -0,0 +1,35 @@
import { fetchAllItems } from "@/app/api/settings/item";
// import ItemsSearch from "./ItemsSearch";
// import ItemsSearchLoading from "./ItemsSearchLoading";
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import { notFound } from "next/navigation";
import { fetchPoWithStockInLines, PoResult } from "@/app/api/po";
import { QcItemWithChecks } from "@/app/api/qc";
import { fetchWarehouseList } from "@/app/api/warehouse";
import { fetchQcItemCheck } from "@/app/api/qc/actions";
import PickOrderDetail from "./PickOrderDetail";
import PickOrderDetailLoading from "./PickOrderDetailLoading";

interface SubComponents {
Loading: typeof PickOrderDetailLoading;
}

type Props = {
consoCode: string;
};

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

return <PickOrderDetail consoCode={consoCode}/>;
};

PoDetailWrapper.Loading = PickOrderDetailLoading;

export default PoDetailWrapper;

+ 1
- 0
src/components/PickOrderDetail/index.ts 查看文件

@@ -0,0 +1 @@
export { default } from "./PickOrderDetailWrapper"

+ 91
- 0
src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx 查看文件

@@ -0,0 +1,91 @@
"use client";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import StyledDataGrid from "../StyledDataGrid";
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { GridColDef } from "@mui/x-data-grid";
import { CircularProgress, Grid, Typography } from "@mui/material";
import { ByItemsSummary } from "@/app/api/pickorder";
import { useTranslation } from "react-i18next";

dayjs.extend(arraySupport);

interface Props {
rows: ByItemsSummary[] | undefined;
setRows: Dispatch<SetStateAction<ByItemsSummary[] | undefined>>;
}

const ConsolidatePickOrderItemSum: React.FC<Props> = ({ rows, setRows }) => {
console.log(rows);
const { t } = useTranslation("pickOrder");

const columns = useMemo<GridColDef[]>(
() => [
{
field: "name",
headerName: "name",
flex: 1,
renderCell: (params) => {
console.log(params.row.name);
return params.row.name;
},
},
{
field: "requiredQty",
headerName: "requiredQty",
flex: 1,
renderCell: (params) => {
console.log(params.row.requiredQty);
const requiredQty = params.row.requiredQty ?? 0;
return `${requiredQty} ${params.row.uomDesc}`;
},
},
{
field: "availableQty",
headerName: "availableQty",
flex: 1,
renderCell: (params) => {
console.log(params.row.availableQty);
const availableQty = params.row.availableQty ?? 0;
return `${availableQty} ${params.row.uomDesc}`;
},
},
],
[]
);
return (
<Grid
container
rowGap={1}
// direction="column"
alignItems="center"
justifyContent="center"
>
<Grid item xs={12}>
<Typography variant="h5" marginInlineEnd={2}>
{t("Items Included")}
</Typography>
</Grid>
<Grid item xs={12}>
{!rows ? (
<CircularProgress size={40} />
) : (
<StyledDataGrid
sx={{ maxHeight: 450 }}
rows={rows}
columns={columns}
/>
)}
</Grid>
</Grid>
);
};

export default ConsolidatePickOrderItemSum;

+ 115
- 0
src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx 查看文件

@@ -0,0 +1,115 @@
"use client";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import StyledDataGrid from "../StyledDataGrid";
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { GridColDef, GridInputRowSelectionModel } from "@mui/x-data-grid";
import { Box, CircularProgress, Grid, Typography } from "@mui/material";
import { PickOrderResult } from "@/app/api/pickorder";
import { useTranslation } from "react-i18next";

dayjs.extend(arraySupport);

interface Props {
consoCode: string;
rows: Omit<PickOrderResult, "items">[] | undefined;
setRows: Dispatch<
SetStateAction<Omit<PickOrderResult, "items">[] | undefined>
>;
revertIds: GridInputRowSelectionModel;
setRevertIds: Dispatch<SetStateAction<GridInputRowSelectionModel>>;
}

const ConsolidatePickOrderSum: React.FC<Props> = ({
consoCode,
rows,
setRows,
revertIds,
setRevertIds,
}) => {
const { t } = useTranslation("pickOrder");
const columns = useMemo<GridColDef[]>(
() => [
{
field: "code",
headerName: "code",
flex: 0.6,
},

{
field: "pickOrderLines",
headerName: "items",
flex: 1,
renderCell: (params) => {
console.log(params);
const pickOrderLine = params.row.pickOrderLines as any[];
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
maxHeight: 100,
overflowY: "scroll",
scrollbarWidth: "none", // For Firefox
"&::-webkit-scrollbar": {
display: "none", // For Chrome, Safari, and Opera
},
}}
>
{pickOrderLine.map((item, index) => (
<Grid sx={{mt:1}}
key={index}
>{`${item.itemName} x ${item.requiredQty} ${item.uomDesc}`}</Grid> // Render each name in a span
))}
</Box>
);
},
},
],
[]
);

return (
<Grid
container
rowGap={1}
// direction="column"
alignItems="center"
justifyContent="center"
>
<Grid item xs={12}>
<Typography variant="h5" marginInlineEnd={2}>
{t("Pick Order Included")}
</Typography>
</Grid>
<Grid item xs={12}>
{!rows ? (
<CircularProgress size={40} />
) : (
<StyledDataGrid
sx={{ maxHeight: 450 }}
checkboxSelection
rowSelectionModel={revertIds}
onRowSelectionModelChange={(newRowSelectionModel) => {
setRevertIds(newRowSelectionModel);
}}
getRowHeight={(params) => {
return 100
}}
rows={rows}
columns={columns}
/>
)}
</Grid>
</Grid>
);
};

export default ConsolidatePickOrderSum;

+ 241
- 6
src/components/PickOrderSearch/ConsolidatedPickOrders.tsx 查看文件

@@ -1,12 +1,247 @@
import {
Box,
Button,
CircularProgress,
Grid,
Modal,
ModalProps,
Typography,
} from "@mui/material";
import { GridToolbarContainer } from "@mui/x-data-grid";
import {
FooterPropsOverrides,
GridColDef,
GridRowSelectionModel,
useGridApiRef,
} from "@mui/x-data-grid";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import SearchResults, {
Column,
defaultPagingController,
} from "../SearchResults/SearchResults";
import { ByItemsSummary, ConsoPickOrderResult, PickOrderLine, PickOrderResult } from "@/app/api/pickorder";
import { useRouter, useSearchParams } from "next/navigation";
import ConsolidatePickOrderItemSum from "./ConsolidatePickOrderItemSum";
import ConsolidatePickOrderSum from "./ConsolidatePickOrderSum";
import { GridInputRowSelectionModel } from "@mui/x-data-grid";
import {
fetchConsoDetail,
fetchConsoPickOrderClient,
} from "@/app/api/pickorder/actions";
import { EditNote } from "@mui/icons-material";

interface Props {

filterArgs: Record<string, any>;
}

const ConsolidatedPickOrders: React.FC<Props> = ({
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
pt: 5,
px: 5,
pb: 10,
width: 1500,
};

}) => {
return <></>
}
const ConsolidatedPickOrders: React.FC<Props> = ({ filterArgs }) => {
const { t } = useTranslation("pickOrder");
const router = useRouter();
const apiRef = useGridApiRef();
const [filteredPickOrders, setFilteredPickOrders] = useState(
[] as ConsoPickOrderResult[]
);
const [isLoading, setIsLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false); //change back to false
const [consoCode, setConsoCode] = useState<string | undefined>(); ///change back to undefined
const [revertIds, setRevertIds] = useState<GridInputRowSelectionModel>([]);
const [totalCount, setTotalCount] = useState<number>();

const [byPickOrderRows, setByPickOrderRows] = useState<Omit<PickOrderResult, "items">[] | undefined>(undefined);
const [byItemsRows, setByItemsRows] = useState<ByItemsSummary[] | undefined>(undefined);

const openDetailModal = useCallback((consoCode: string) => {
setConsoCode(consoCode);
setModalOpen(true);
}, []);

const closeDetailModal = useCallback(() => {
setModalOpen(false);
setConsoCode(undefined);
}, []);

const onDetailClick = useCallback(
(pickOrder: any) => {
console.log(pickOrder);
openDetailModal(pickOrder.consoCode);
},
[openDetailModal]
);
const columns = useMemo<Column<ConsoPickOrderResult>[]>(
() => [
{
name: "id",
label: t("Detail"),
onClick: onDetailClick,
buttonIcon: <EditNote />,
},
{
name: "consoCode",
label: t("consoCode"),
},
],
[]
);
const [pagingController, setPagingController] = useState(
defaultPagingController
);

// pass conso code back to assign
// pass user back to assign
const fetchNewPageConsoPickOrder = useCallback(
async (
pagingController: Record<string, number>,
filterArgs: Record<string, number>
) => {
setIsLoading(true);
const params = {
...pagingController,
...filterArgs,
};
const res = await fetchConsoPickOrderClient(params);
if (res) {
console.log(res);
setFilteredPickOrders(res.records);
setTotalCount(res.total);
}
setIsLoading(false);
},
[]
);

useEffect(() => {
fetchNewPageConsoPickOrder(pagingController, filterArgs);
}, [fetchNewPageConsoPickOrder, pagingController, filterArgs]);

const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
(...args) => {
closeDetailModal();
// reset();
},
[closeDetailModal]
);

const handleRelease = useCallback(() => {
console.log("release");
router.push(`/pickorder/detail?consoCode=${consoCode}`);
}, [router, consoCode]);

const handleConsolidate_revert = useCallback(() => {
console.log(revertIds);
}, [revertIds]);

const fetchConso = useCallback(async (consoCode: string) => {
const res = await fetchConsoDetail(consoCode);
if (res) {
console.log(res);
setByPickOrderRows(res.pickOrders)
setByItemsRows(res.items)
} else {
console.log("error");
console.log(res);
}
}, []);

useEffect(() => {
if (consoCode) {
fetchConso(consoCode);
}
}, [consoCode]);
return (
<>
<Grid
container
rowGap={1}
// direction="column"
alignItems="center"
justifyContent="center"
>
<Grid item xs={12}>
{isLoading ? (
<CircularProgress size={40} />
) : (
<SearchResults<ConsoPickOrderResult>
items={filteredPickOrders}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
totalCount={totalCount}
/>
)}
</Grid>
</Grid>
{consoCode != undefined ? (
<Modal open={modalOpen} onClose={closeHandler}>
<Box sx={{ ...style, maxHeight: 800 }}>
<Typography mb={2} variant="h4">
{consoCode}
</Typography>
<Box sx={{
height: 400,
overflowY: "auto"
}}>
<Grid container>
<Grid item xs={12} sx={{ mt: 2 }}>
<ConsolidatePickOrderSum
rows={byPickOrderRows}
setRows={setByPickOrderRows}
consoCode={consoCode}
revertIds={revertIds}
setRevertIds={setRevertIds}
/>
</Grid>
<Grid item xs={12}>
<ConsolidatePickOrderItemSum
rows={byItemsRows}
setRows={setByItemsRows}
/>
</Grid>
</Grid>
</Box>
<Grid container>
<Grid
item
xs={12}
display="flex"
justifyContent="end"
alignItems="end"
>
<Button
disabled={(revertIds as number[]).length < 1}
variant="outlined"
onClick={handleConsolidate_revert}
sx={{ mr: 1 }}
>
{t("remove")}
</Button>
<Button
// disabled={selectedRows.length < 1}
variant="outlined"
onClick={handleRelease}
>
{t("release")}
</Button>
</Grid>
</Grid>
</Box>
</Modal>
) : undefined}
</>
);
};

export default ConsolidatedPickOrders;
export default ConsolidatedPickOrders;

+ 152
- 81
src/components/PickOrderSearch/PickOrderSearch.tsx 查看文件

@@ -1,102 +1,173 @@
"use client"
import { PickOrderResult } from "@/app/api/pickOrder";
"use client";
import { PickOrderResult } from "@/app/api/pickorder";
import { SearchParams } from "@/app/utils/fetchUtil";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import SearchBox, { Criterion } from "../SearchBox";
import SearchResults, { Column } from "../SearchResults";
import { flatten, groupBy, intersectionWith, isEmpty, map, sortBy, sortedUniq, uniqBy, upperCase, upperFirst } from "lodash";
import { arrayToDateString, arrayToDayjs, dateStringToDayjs } from "@/app/utils/formatUtil";
import {
flatten,
groupBy,
intersectionWith,
isEmpty,
map,
sortBy,
sortedUniq,
uniqBy,
upperCase,
upperFirst,
} from "lodash";
import {
arrayToDateString,
arrayToDayjs,
dateStringToDayjs,
} from "@/app/utils/formatUtil";
import dayjs from "dayjs";
import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material";
import PickOrders from "./PickOrders";
import ConsolidatedPickOrders from "./ConsolidatedPickOrders";

interface Props {
pickOrders: PickOrderResult[];
pickOrders: PickOrderResult[];
}

type SearchQuery = Partial<Omit<PickOrderResult,
| "id"
| "consoCode"
| "completeDate">>
type SearchQuery = Partial<
Omit<PickOrderResult, "id" | "consoCode" | "completeDate">
>;

type SearchParamNames = keyof SearchQuery;

const PickOrderSearch: React.FC<Props> = ({
pickOrders,
}) => {
const { t } = useTranslation("pickOrders");
const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
const { t } = useTranslation("pickOrders");

const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders)
const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders);
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({});
const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[]
);

const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{ label: t("Code"), paramName: "code", type: "text" },
{
label: t("Target Date From"),
label2: t("Target Date To"),
paramName: "targetDate",
type: "dateRange",
},
{
label: t("Type"),
paramName: "type",
type: "autocomplete",
options: sortBy(
uniqBy(
pickOrders.map((po) => ({
value: po.type,
label: t(upperCase(po.type)),
})),
"value"
),
"label"
),
},
{
label: t("Status"),
paramName: "status",
type: "autocomplete",
options: sortBy(
uniqBy(
pickOrders.map((po) => ({
value: po.status,
label: t(upperFirst(po.status)),
})),
"value"
),
"label"
),
},
{
label: t("Items"),
paramName: "items",
type: "autocomplete", // multiple: true,
options: uniqBy(
flatten(
sortBy(
pickOrders.map((po) =>
po.items
? po.items.map((item) => ({
value: item.name,
label: item.name,
// group: item.type
}))
: []
),
"label"
)
),
"value"
),
},
],
[t]
);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Target Date From"), label2: t("Target Date To"), paramName: "targetDate", type: "dateRange" },
{
label: t("Type"), paramName: "type", type: "autocomplete",
options: sortBy(
uniqBy(pickOrders.map((po) => ({ value: po.type, label: t(upperCase(po.type)) })), "value"),
"label")
},
{
label: t("Status"), paramName: "status", type: "autocomplete",
options: sortBy(
uniqBy(pickOrders.map((po) => ({ value: po.status, label: t(upperFirst(po.status)) })), "value"),
"label")
},
{
label: t("Items"), paramName: "items", type: "autocomplete", // multiple: true,
options: uniqBy(flatten(sortBy(
pickOrders.map((po) => po.items ? po.items.map((item) => ({
value: item.name, label: item.name,
// group: item.type
})) : []),
"label")), "value")
},
], [t])
const onReset = useCallback(() => {
setFilteredPickOrders(pickOrders);
}, [pickOrders]);

const onReset = useCallback(() => {
setFilteredPickOrders(pickOrders)
}, [pickOrders])
return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilterArgs({ ...query }); // modify later
setFilteredPickOrders(
pickOrders.filter((po) => {
const poTargetDateStr = arrayToDayjs(po.targetDate);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
setFilteredPickOrders(
pickOrders.filter(
(po) => {
const poTargetDateStr = arrayToDayjs(po.targetDate)
// console.log(intersectionWith(po.items?.map(item => item.name), query.items))
return (
po.code.toLowerCase().includes(query.code.toLowerCase()) &&
(isEmpty(query.targetDate) ||
poTargetDateStr.isSame(query.targetDate) ||
poTargetDateStr.isAfter(query.targetDate)) &&
(isEmpty(query.targetDateTo) ||
poTargetDateStr.isSame(query.targetDateTo) ||
poTargetDateStr.isBefore(query.targetDateTo)) &&
(intersectionWith(["All"], query.items).length > 0 ||
intersectionWith(
po.items?.map((item) => item.name),
query.items
).length > 0) &&
(query.status.toLowerCase() == "all" ||
po.status
.toLowerCase()
.includes(query.status.toLowerCase())) &&
(query.type.toLowerCase() == "all" ||
po.type.toLowerCase().includes(query.type.toLowerCase()))
);
})
);
}}
onReset={onReset}
/>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Pick Orders")} iconPosition="end" />
<Tab label={t("Consolidated Pick Orders")} iconPosition="end" />
</Tabs>
{tabIndex === 0 && (
<PickOrders
filteredPickOrders={filteredPickOrders}
filterArgs={filterArgs}
/>
)}
{tabIndex === 1 && <ConsolidatedPickOrders filterArgs={filterArgs} />}
</>
);
};

// console.log(intersectionWith(po.items?.map(item => item.name), query.items))
return po.code.toLowerCase().includes(query.code.toLowerCase())
&& (isEmpty(query.targetDate) || poTargetDateStr.isSame(query.targetDate) || poTargetDateStr.isAfter(query.targetDate))
&& (isEmpty(query.targetDateTo) || poTargetDateStr.isSame(query.targetDateTo) || poTargetDateStr.isBefore(query.targetDateTo))
&& (intersectionWith(["All"], query.items).length > 0 || intersectionWith(po.items?.map(item => item.name), query.items).length > 0)
&& (query.status.toLowerCase() == "all" || po.status.toLowerCase().includes(query.status.toLowerCase()))
&& (query.type.toLowerCase() == "all" || po.type.toLowerCase().includes(query.type.toLowerCase()))
}
)
)
}}
onReset={onReset}
/>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Pick Orders")} iconPosition="end" />
<Tab label={t("Consolidated Pick Orders")} iconPosition="end" />
</Tabs>
{tabIndex === 0 && <PickOrders filteredPickOrders={filteredPickOrders}/>}
</>
)
}

export default PickOrderSearch;
export default PickOrderSearch;

+ 9
- 2
src/components/PickOrderSearch/PickOrderSearchWrapper.tsx 查看文件

@@ -1,4 +1,4 @@
import { fetchPickOrders } from "@/app/api/pickOrder";
import { fetchPickOrders } from "@/app/api/pickorder";
import GeneralLoading from "../General/GeneralLoading";
import PickOrderSearch from "./PickOrderSearch";

@@ -10,7 +10,14 @@ const PickOrderSearchWrapper: React.FC & SubComponents = async () => {
const [
pickOrders
] = await Promise.all([
fetchPickOrders()
fetchPickOrders({
code: undefined,
targetDateFrom: undefined,
targetDateTo: undefined,
type: undefined,
status: undefined,
itemName: undefined,
})
])

return <PickOrderSearch pickOrders={pickOrders}/>


+ 138
- 81
src/components/PickOrderSearch/PickOrders.tsx 查看文件

@@ -1,100 +1,157 @@
import { Button, Grid } from "@mui/material";
import { Button, CircularProgress, Grid } from "@mui/material";
import SearchResults, { Column } from "../SearchResults/SearchResults";
import { PickOrderResult } from "@/app/api/pickOrder";
import { PickOrderResult } from "@/app/api/pickorder";
import { useTranslation } from "react-i18next";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { isEmpty, upperCase, upperFirst } from "lodash";
import { arrayToDateString } from "@/app/utils/formatUtil";
import { consolidatePickOrder, fetchPickOrderClient } from "@/app/api/pickorder/actions";
import useUploadContext from "../UploadProvider/useUploadContext";

interface Props {
filteredPickOrders: PickOrderResult[],
filteredPickOrders: PickOrderResult[];
filterArgs: Record<string, any>;
}

const PickOrders: React.FC<Props> = ({
filteredPickOrders
}) => {
const { t } = useTranslation("pickOrder")
const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]);
const PickOrders: React.FC<Props> = ({ filteredPickOrders, filterArgs }) => {
const { t } = useTranslation("pickOrder");
const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]);
const [filteredPickOrder, setFilteredPickOrder] = useState(
[] as PickOrderResult[]
);
const { setIsUploading } = useUploadContext();
const [isLoading, setIsLoading] = useState(false);
const [pagingController, setPagingController] = useState({
pageNum: 0,
pageSize: 10,
});
const [totalCount, setTotalCount] = useState<number>();

const handleConsolidatedRows = useCallback(() => {
const handleConsolidatedRows = useCallback(async () => {
console.log(selectedRows);
setIsUploading(true);
try {
const res = await consolidatePickOrder(selectedRows as number[]);
if (res) {
console.log(res);
}
} catch {
setIsUploading(false);
}
fetchNewPagePickOrder(pagingController, filterArgs);
setIsUploading(false);
}, [selectedRows, pagingController]);

}, [selectedRows])
const fetchNewPagePickOrder = useCallback(
async (
pagingController: Record<string, number>,
filterArgs: Record<string, number>
) => {
setIsLoading(true);
const params = {
...pagingController,
...filterArgs,
};
const res = await fetchPickOrderClient(params)
if (res) {
console.log(res);
setFilteredPickOrder(res.records);
setTotalCount(res.total);
}
setIsLoading(false);
},
[]
);

const columns = useMemo<Column<PickOrderResult>[]>(() => [
{
name: "id",
label: "",
type: "checkbox",
disabled: (params) => {
return !isEmpty(params.consoCode);
}
},
{
name: "code",
label: t("Code"),
},
{
name: "consoCode",
label: t("Consolidated Code"),
renderCell: (params) => {
return params.consoCode ?? "N/A"
}
useEffect(() => {
fetchNewPagePickOrder(pagingController, filterArgs);
}, [fetchNewPagePickOrder, pagingController, filterArgs]);

const columns = useMemo<Column<PickOrderResult>[]>(
() => [
{
name: "id",
label: "",
type: "checkbox",
disabled: (params) => {
return !isEmpty(params.consoCode);
},
{
name: "type",
label: t("type"),
renderCell: (params) => {
return upperCase(params.type)
}
},
{
name: "code",
label: t("Code"),
},
{
name: "consoCode",
label: t("Consolidated Code"),
renderCell: (params) => {
return params.consoCode ?? "";
},
{
name: "items",
label: t("Items"),
renderCell: (params) => {
return params.items?.map((i) => i.name).join(", ")
}
},
{
name: "type",
label: t("type"),
renderCell: (params) => {
return upperCase(params.type);
},
{
name: "targetDate",
label: t("Target Date"),
renderCell: (params) => {
return arrayToDateString(params.targetDate)
}
},
{
name: "items",
label: t("Items"),
renderCell: (params) => {
return params.items?.map((i) => i.name).join(", ");
},
{
name: "releasedBy",
label: t("Released By"),
},
{
name: "targetDate",
label: t("Target Date"),
renderCell: (params) => {
return arrayToDateString(params.targetDate);
},
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return upperFirst(params.status)
}
},
{
name: "releasedBy",
label: t("Released By"),
},
{
name: "status",
label: t("Status"),
renderCell: (params) => {
return upperFirst(params.status);
},
], [t])
},
],
[t]
);

return (
<Grid container rowGap={1}>
<Grid item xs={3}>
<Button
disabled={selectedRows.length < 1}
variant="outlined"
>
{t("Consolidate")}
</Button>
</Grid>
<Grid item xs={12}>
<SearchResults<PickOrderResult> items={filteredPickOrders} columns={columns} pagingController={{
pageNum: 0,
pageSize: 0
}}
checkboxIds={selectedRows}
setCheckboxIds={setSelectedRows}
/>
</Grid>
</Grid>
)
}
return (
<Grid container rowGap={1}>
<Grid item xs={3}>
<Button
disabled={selectedRows.length < 1}
variant="outlined"
onClick={handleConsolidatedRows}
>
{t("Consolidate")}
</Button>
</Grid>
<Grid item xs={12}>
{isLoading ? (
<CircularProgress size={40} />
) : (
<SearchResults<PickOrderResult>
items={filteredPickOrder}
columns={columns}
pagingController={pagingController}
setPagingController={setPagingController}
totalCount={totalCount}
checkboxIds={selectedRows!!}
setCheckboxIds={setSelectedRows}
/>
)}
</Grid>
</Grid>
);
};

export default PickOrders;
export default PickOrders;

+ 1
- 1
src/components/PoSearch/PoSearch.tsx 查看文件

@@ -152,7 +152,7 @@ const PoSearch: React.FC<Props> = ({
setTotalCount(res.total);
}
},
[fetchPoListClient, pagingController]
[fetchPoListClient]
);

useEffect(() => {


+ 1
- 0
src/i18n/zh/common.json 查看文件

@@ -2,6 +2,7 @@
"Overview": "概述",
"Qc Item": "品質檢驗項目",
"Dashboard": "儀表板",
"dashboard": "儀表板",
"Raw Material": "原料",
"Purchase Order": "採購訂單",
"Pick Order": "提料單",


正在加载...
取消
保存