@@ -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,4 +1,4 @@ | |||||
import { PreloadPickOrder } from "@/app/api/pickOrder"; | |||||
import { PreloadPickOrder } from "@/app/api/pickorder"; | |||||
import PickOrderSearch from "@/components/PickOrderSearch"; | import PickOrderSearch from "@/components/PickOrderSearch"; | ||||
import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
import { Stack, Typography } from "@mui/material"; | import { Stack, Typography } from "@mui/material"; | ||||
@@ -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"] }, | |||||
}); | |||||
}); |
@@ -1,5 +1,5 @@ | |||||
import "server-only"; | import "server-only"; | ||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { cache } from "react"; | import { cache } from "react"; | ||||
@@ -18,14 +18,74 @@ export interface PickOrderResult{ | |||||
status: string, | status: string, | ||||
releasedBy: string, | releasedBy: string, | ||||
items?: PickOrderItemInfo[] | null, | 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 = () => { | 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: { | next: { | ||||
tags: ["pickOrders"] | tags: ["pickOrders"] | ||||
} | } | ||||
@@ -4,8 +4,10 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
export interface QrCodeInfo { | export interface QrCodeInfo { | ||||
stockInLineId?: number; | |||||
itemId: number | |||||
warehouseId?: number | |||||
lotNo?: string | |||||
} | |||||
// warehouse qrcode | |||||
warehouseId?: number | |||||
// item qrcode | |||||
stockInLineId?: number; | |||||
itemId: number | |||||
lotNo?: string | |||||
} |
@@ -3,6 +3,11 @@ import { getServerSession } from "next-auth"; | |||||
import { headers } from "next/headers"; | import { headers } from "next/headers"; | ||||
import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
export interface Pageable { | |||||
pageSize?: number | |||||
pageNum?: number | |||||
} | |||||
export type SearchParams = { | export type SearchParams = { | ||||
searchParams: { [key: string]: string | string[] | undefined }; | searchParams: { [key: string]: string | string[] | undefined }; | ||||
} | } | ||||
@@ -24,6 +24,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/do": "Delivery Order", | "/do": "Delivery Order", | ||||
"/pickOrder": "Pick Order", | "/pickOrder": "Pick Order", | ||||
"/po": "Purchase Order", | "/po": "Purchase Order", | ||||
"/dashboard": "dashboard", | |||||
}; | }; | ||||
const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
@@ -52,7 +52,7 @@ const NavigationContent: React.FC = () => { | |||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "Pick Order", | label: "Pick Order", | ||||
path: "/pickOrder", | |||||
path: "/pickorder", | |||||
}, | }, | ||||
// { | // { | ||||
// icon: <RequestQuote />, | // icon: <RequestQuote />, | ||||
@@ -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; |
@@ -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; |
@@ -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; |
@@ -0,0 +1 @@ | |||||
export { default } from "./PickOrderDetailWrapper" |
@@ -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; |
@@ -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; |
@@ -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 { | 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; |
@@ -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 { SearchParams } from "@/app/utils/fetchUtil"; | ||||
import { useCallback, useMemo, useState } from "react"; | import { useCallback, useMemo, useState } from "react"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
import SearchResults, { Column } from "../SearchResults"; | 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 dayjs from "dayjs"; | ||||
import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | import { Button, Grid, Stack, Tab, Tabs, TabsProps } from "@mui/material"; | ||||
import PickOrders from "./PickOrders"; | import PickOrders from "./PickOrders"; | ||||
import ConsolidatedPickOrders from "./ConsolidatedPickOrders"; | |||||
interface Props { | 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; | 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; |
@@ -1,4 +1,4 @@ | |||||
import { fetchPickOrders } from "@/app/api/pickOrder"; | |||||
import { fetchPickOrders } from "@/app/api/pickorder"; | |||||
import GeneralLoading from "../General/GeneralLoading"; | import GeneralLoading from "../General/GeneralLoading"; | ||||
import PickOrderSearch from "./PickOrderSearch"; | import PickOrderSearch from "./PickOrderSearch"; | ||||
@@ -10,7 +10,14 @@ const PickOrderSearchWrapper: React.FC & SubComponents = async () => { | |||||
const [ | const [ | ||||
pickOrders | pickOrders | ||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
fetchPickOrders() | |||||
fetchPickOrders({ | |||||
code: undefined, | |||||
targetDateFrom: undefined, | |||||
targetDateTo: undefined, | |||||
type: undefined, | |||||
status: undefined, | |||||
itemName: undefined, | |||||
}) | |||||
]) | ]) | ||||
return <PickOrderSearch pickOrders={pickOrders}/> | return <PickOrderSearch pickOrders={pickOrders}/> | ||||
@@ -1,100 +1,157 @@ | |||||
import { Button, Grid } from "@mui/material"; | |||||
import { Button, CircularProgress, Grid } from "@mui/material"; | |||||
import SearchResults, { Column } from "../SearchResults/SearchResults"; | import SearchResults, { Column } from "../SearchResults/SearchResults"; | ||||
import { PickOrderResult } from "@/app/api/pickOrder"; | |||||
import { PickOrderResult } from "@/app/api/pickorder"; | |||||
import { useTranslation } from "react-i18next"; | 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 { isEmpty, upperCase, upperFirst } from "lodash"; | ||||
import { arrayToDateString } from "@/app/utils/formatUtil"; | import { arrayToDateString } from "@/app/utils/formatUtil"; | ||||
import { consolidatePickOrder, fetchPickOrderClient } from "@/app/api/pickorder/actions"; | |||||
import useUploadContext from "../UploadProvider/useUploadContext"; | |||||
interface Props { | 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; |
@@ -152,7 +152,7 @@ const PoSearch: React.FC<Props> = ({ | |||||
setTotalCount(res.total); | setTotalCount(res.total); | ||||
} | } | ||||
}, | }, | ||||
[fetchPoListClient, pagingController] | |||||
[fetchPoListClient] | |||||
); | ); | ||||
useEffect(() => { | useEffect(() => { | ||||
@@ -2,6 +2,7 @@ | |||||
"Overview": "概述", | "Overview": "概述", | ||||
"Qc Item": "品質檢驗項目", | "Qc Item": "品質檢驗項目", | ||||
"Dashboard": "儀表板", | "Dashboard": "儀表板", | ||||
"dashboard": "儀表板", | |||||
"Raw Material": "原料", | "Raw Material": "原料", | ||||
"Purchase Order": "採購訂單", | "Purchase Order": "採購訂單", | ||||
"Pick Order": "提料單", | "Pick Order": "提料單", | ||||