From 4c7c12d76cb6b712405c1ef82fc4e37b9b2ead42 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 25 Jun 2025 18:26:20 +0800 Subject: [PATCH] update --- src/app/(main)/pickOrder/detail/page.tsx | 30 ++ src/app/(main)/pickOrder/page.tsx | 2 +- src/app/api/pickOrder/actions.ts | 84 ++++++ src/app/api/pickOrder/index.ts | 68 ++++- src/app/api/qrcode/index.ts | 12 +- src/app/utils/fetchUtil.ts | 5 + src/components/Breadcrumb/Breadcrumb.tsx | 1 + .../NavigationContent/NavigationContent.tsx | 2 +- .../PickOrderDetail/PickOrderDetail.tsx | 276 ++++++++++++++++++ .../PickOrderDetailLoading.tsx | 40 +++ .../PickOrderDetailWrapper.tsx | 35 +++ src/components/PickOrderDetail/index.ts | 1 + .../ConsolidatePickOrderItemSum.tsx | 91 ++++++ .../ConsolidatePickOrderSum.tsx | 115 ++++++++ .../ConsolidatedPickOrders.tsx | 247 +++++++++++++++- .../PickOrderSearch/PickOrderSearch.tsx | 233 ++++++++++----- .../PickOrderSearchWrapper.tsx | 11 +- src/components/PickOrderSearch/PickOrders.tsx | 219 +++++++++----- src/components/PoSearch/PoSearch.tsx | 2 +- src/i18n/zh/common.json | 1 + 20 files changed, 1293 insertions(+), 182 deletions(-) create mode 100644 src/app/(main)/pickOrder/detail/page.tsx create mode 100644 src/components/PickOrderDetail/PickOrderDetail.tsx create mode 100644 src/components/PickOrderDetail/PickOrderDetailLoading.tsx create mode 100644 src/components/PickOrderDetail/PickOrderDetailWrapper.tsx create mode 100644 src/components/PickOrderDetail/index.ts create mode 100644 src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx create mode 100644 src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx diff --git a/src/app/(main)/pickOrder/detail/page.tsx b/src/app/(main)/pickOrder/detail/page.tsx new file mode 100644 index 0000000..fa4b09e --- /dev/null +++ b/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 = async ({ searchParams }) => { + const { t } = await getServerI18n("pickOrder"); + + PreloadPickOrder(); + + return ( + <> + + }> + + + + + ); +}; + +export default PickOrder; diff --git a/src/app/(main)/pickOrder/page.tsx b/src/app/(main)/pickOrder/page.tsx index 740f99a..c36ed1c 100644 --- a/src/app/(main)/pickOrder/page.tsx +++ b/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"; diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index e69de29..888f994 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/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(`${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(`${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) => { + if (queryParams) { + const queryString = new URLSearchParams(queryParams).toString(); + return serverFetchJson>(`${BASE_API_URL}/pickOrder/getRecordByPage?${queryString}`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } else { + return serverFetchJson>(`${BASE_API_URL}/pickOrder/getRecordByPage`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } + }); + + export const fetchConsoPickOrderClient = cache(async (queryParams?: Record) => { + if (queryParams) { + const queryString = new URLSearchParams(queryParams).toString(); + return serverFetchJson>(`${BASE_API_URL}/pickOrder/getRecordByPage-conso?${queryString}`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } else { + return serverFetchJson>(`${BASE_API_URL}/pickOrder/getRecordByPage-conso`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } + }); + + export const fetchConsoPickOrderLineClient = cache(async (queryParams?: Record) => { + if (queryParams) { + const queryString = new URLSearchParams(queryParams).toString(); + return serverFetchJson>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage?${queryString}`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } else { + return serverFetchJson>(`${BASE_API_URL}/pickOrder/get-pickorder-line-byPage`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + } + }); + + export const fetchConsoDetail = cache(async (consoCode: string) => { + return serverFetchJson(`${BASE_API_URL}/pickOrder/releaseConso/${consoCode}`, { + method: 'GET', + next: { tags: ["pickorder"] }, + }); + }); \ No newline at end of file diff --git a/src/app/api/pickOrder/index.ts b/src/app/api/pickOrder/index.ts index 6958b98..29b3fc2 100644 --- a/src/app/api/pickOrder/index.ts +++ b/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[] + 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(`${BASE_API_URL}/pickOrder/list`, { +export const fetchPickOrders = cache(async (queryParams: FetchPickOrders) => { + const queryString = new URLSearchParams(queryParams as Record).toString(); + return serverFetchJson(`${BASE_API_URL}/pickOrder/list?${queryString}`, { next: { tags: ["pickOrders"] } diff --git a/src/app/api/qrcode/index.ts b/src/app/api/qrcode/index.ts index 04ce08f..c738482 100644 --- a/src/app/api/qrcode/index.ts +++ b/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 - } \ No newline at end of file + // warehouse qrcode + warehouseId?: number + // item qrcode + stockInLineId?: number; + itemId: number + lotNo?: string +} \ No newline at end of file diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index 053957a..a925dd0 100644 --- a/src/app/utils/fetchUtil.ts +++ b/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 }; } diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index cedc17f..b52993f 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/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 = () => { diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 2c34365..9b2e182 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -52,7 +52,7 @@ const NavigationContent: React.FC = () => { { icon: , label: "Pick Order", - path: "/pickOrder", + path: "/pickorder", }, // { // icon: , diff --git a/src/components/PickOrderDetail/PickOrderDetail.tsx b/src/components/PickOrderDetail/PickOrderDetail.tsx new file mode 100644 index 0000000..beec513 --- /dev/null +++ b/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 = ({ consoCode }) => { + const { t } = useTranslation("pickOrder"); + const [selectedRow, setSelectRow] = useState(); + const [isLoadingModel, setIsLoadingModel] = useState({ + pickOrderLineTable: false, + stockOutLineTable: false, + }); + const [criteriaArgs, setCriteriaArgs] = useState({ + pageNum: 1, + pageSize: 10, + }); + const [polTotalCount, setPolTotalCount] = useState(0); + const [solTotalCount, setSolTotalCount] = useState(0); + + const [suggestedList, setSuggestedList] = useState< + PickOrderLineWithSuggestedLot[] + >([]); + + const sugggestedLotColumn = useMemo( + () => [ + { + 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( + () => [ + { + 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: , + 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: , + 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: , + 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) => { + 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 ( + <> + + + + + {consoCode} + + + + + + + + + + + {/* + + */} + + {isLoadingModel.pickOrderLineTable ? ( + + ) : ( + { + setSelectRow(newRowSelectionModel); + }} + pageSizeOptions={[2, 10, 25, 50, 100]} + onPaginationModelChange={async (model, details) => { + setCriteriaArgs({ + pageNum: model.page + 1, + pageSize: model.pageSize, + }); + }} + rowCount={polTotalCount} + /> + )} + + + + + + + + ); +}; +export default PickOrderDetail; diff --git a/src/components/PickOrderDetail/PickOrderDetailLoading.tsx b/src/components/PickOrderDetail/PickOrderDetailLoading.tsx new file mode 100644 index 0000000..7fc248b --- /dev/null +++ b/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 ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default PickOrderDetailLoading; diff --git a/src/components/PickOrderDetail/PickOrderDetailWrapper.tsx b/src/components/PickOrderDetail/PickOrderDetailWrapper.tsx new file mode 100644 index 0000000..f201af7 --- /dev/null +++ b/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 & SubComponents = async ({ consoCode }) => { +// const [poWithStockInLine, warehouse, qc] = await Promise.all([ +// fetchPoWithStockInLines(id), +// fetchWarehouseList(), +// fetchQcItemCheck(), +// ]); + // const poWithStockInLine = await fetchPoWithStockInLines(id) + + return ; +}; + +PoDetailWrapper.Loading = PickOrderDetailLoading; + +export default PoDetailWrapper; diff --git a/src/components/PickOrderDetail/index.ts b/src/components/PickOrderDetail/index.ts new file mode 100644 index 0000000..47ec0ac --- /dev/null +++ b/src/components/PickOrderDetail/index.ts @@ -0,0 +1 @@ +export { default } from "./PickOrderDetailWrapper" \ No newline at end of file diff --git a/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx b/src/components/PickOrderSearch/ConsolidatePickOrderItemSum.tsx new file mode 100644 index 0000000..7e30b64 --- /dev/null +++ b/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>; +} + +const ConsolidatePickOrderItemSum: React.FC = ({ rows, setRows }) => { + console.log(rows); + const { t } = useTranslation("pickOrder"); + + const columns = useMemo( + () => [ + { + 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 ( + + + + {t("Items Included")} + + + + {!rows ? ( + + ) : ( + + )} + + + ); +}; + +export default ConsolidatePickOrderItemSum; diff --git a/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx b/src/components/PickOrderSearch/ConsolidatePickOrderSum.tsx new file mode 100644 index 0000000..b4fd99e --- /dev/null +++ b/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[] | undefined; + setRows: Dispatch< + SetStateAction[] | undefined> + >; + revertIds: GridInputRowSelectionModel; + setRevertIds: Dispatch>; +} + +const ConsolidatePickOrderSum: React.FC = ({ + consoCode, + rows, + setRows, + revertIds, + setRevertIds, +}) => { + const { t } = useTranslation("pickOrder"); + const columns = useMemo( + () => [ + { + 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 ( + + {pickOrderLine.map((item, index) => ( + {`${item.itemName} x ${item.requiredQty} ${item.uomDesc}`} // Render each name in a span + ))} + + ); + }, + }, + ], + [] + ); + + return ( + + + + {t("Pick Order Included")} + + + + {!rows ? ( + + ) : ( + { + setRevertIds(newRowSelectionModel); + }} + getRowHeight={(params) => { + return 100 + }} + rows={rows} + columns={columns} + /> + )} + + + ); +}; + +export default ConsolidatePickOrderSum; diff --git a/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx b/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx index 445b59a..1dd6445 100644 --- a/src/components/PickOrderSearch/ConsolidatedPickOrders.tsx +++ b/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; } -const ConsolidatedPickOrders: React.FC = ({ +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 = ({ 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(); ///change back to undefined + const [revertIds, setRevertIds] = useState([]); + const [totalCount, setTotalCount] = useState(); + + const [byPickOrderRows, setByPickOrderRows] = useState[] | undefined>(undefined); + const [byItemsRows, setByItemsRows] = useState(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[]>( + () => [ + { + name: "id", + label: t("Detail"), + onClick: onDetailClick, + buttonIcon: , + }, + { + 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, + filterArgs: Record + ) => { + 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>( + (...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 ( + <> + + + {isLoading ? ( + + ) : ( + + items={filteredPickOrders} + columns={columns} + pagingController={pagingController} + setPagingController={setPagingController} + totalCount={totalCount} + /> + )} + + + {consoCode != undefined ? ( + + + + {consoCode} + + + + + + + + + + + + + + + + + + + + ) : undefined} + + ); +}; -export default ConsolidatedPickOrders; \ No newline at end of file +export default ConsolidatedPickOrders; diff --git a/src/components/PickOrderSearch/PickOrderSearch.tsx b/src/components/PickOrderSearch/PickOrderSearch.tsx index eb22d9b..366c5eb 100644 --- a/src/components/PickOrderSearch/PickOrderSearch.tsx +++ b/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> +type SearchQuery = Partial< + Omit +>; type SearchParamNames = keyof SearchQuery; -const PickOrderSearch: React.FC = ({ - pickOrders, -}) => { - const { t } = useTranslation("pickOrders"); +const PickOrderSearch: React.FC = ({ pickOrders }) => { + const { t } = useTranslation("pickOrders"); - const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders) + const [filteredPickOrders, setFilteredPickOrders] = useState(pickOrders); + const [filterArgs, setFilterArgs] = useState>({}); + const [tabIndex, setTabIndex] = useState(0); + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); - const [tabIndex, setTabIndex] = useState(0); - const handleTabChange = useCallback>( - (_e, newValue) => { - setTabIndex(newValue); - }, - [], - ); + const searchCriteria: Criterion[] = 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[] = 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 ( + <> + { + setFilterArgs({ ...query }); // modify later + setFilteredPickOrders( + pickOrders.filter((po) => { + const poTargetDateStr = arrayToDayjs(po.targetDate); - return ( - <> - { - 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} + /> + + + + + {tabIndex === 0 && ( + + )} + {tabIndex === 1 && } + + ); +}; - // 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} - /> - - - - - {tabIndex === 0 && } - - ) -} - -export default PickOrderSearch; \ No newline at end of file +export default PickOrderSearch; diff --git a/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx b/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx index 0d19df7..a06ae97 100644 --- a/src/components/PickOrderSearch/PickOrderSearchWrapper.tsx +++ b/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 diff --git a/src/components/PickOrderSearch/PickOrders.tsx b/src/components/PickOrderSearch/PickOrders.tsx index ea63b68..910962e 100644 --- a/src/components/PickOrderSearch/PickOrders.tsx +++ b/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; } -const PickOrders: React.FC = ({ - filteredPickOrders -}) => { - const { t } = useTranslation("pickOrder") - const [selectedRows, setSelectedRows] = useState<(string | number)[]>([]); +const PickOrders: React.FC = ({ 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(); - 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, + filterArgs: Record + ) => { + 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[]>(() => [ - { - 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[]>( + () => [ + { + 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 ( - - - - - - items={filteredPickOrders} columns={columns} pagingController={{ - pageNum: 0, - pageSize: 0 - }} - checkboxIds={selectedRows} - setCheckboxIds={setSelectedRows} - /> - - - ) -} + return ( + + + + + + {isLoading ? ( + + ) : ( + + items={filteredPickOrder} + columns={columns} + pagingController={pagingController} + setPagingController={setPagingController} + totalCount={totalCount} + checkboxIds={selectedRows!!} + setCheckboxIds={setSelectedRows} + /> + )} + + + ); +}; -export default PickOrders; \ No newline at end of file +export default PickOrders; diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index 67db55c..c50ac0e 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -152,7 +152,7 @@ const PoSearch: React.FC = ({ setTotalCount(res.total); } }, - [fetchPoListClient, pagingController] + [fetchPoListClient] ); useEffect(() => { diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 5b72cac..0badeb5 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -2,6 +2,7 @@ "Overview": "概述", "Qc Item": "品質檢驗項目", "Dashboard": "儀表板", + "dashboard": "儀表板", "Raw Material": "原料", "Purchase Order": "採購訂單", "Pick Order": "提料單",