| @@ -140,3 +140,18 @@ export const fetchPoInClient = cache(async (id: number) => { | |||
| } | |||
| }); | |||
| export const testing = cache(async (queryParams?: Record<string, any>) => { | |||
| if (queryParams) { | |||
| const queryString = new URLSearchParams(queryParams).toString(); | |||
| return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing?${queryString}`, { | |||
| method: 'GET', | |||
| next: { tags: ["po"] }, | |||
| }); | |||
| } else { | |||
| return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing`, { | |||
| method: 'GET', | |||
| next: { tags: ["po"] }, | |||
| }); | |||
| } | |||
| }); | |||
| @@ -295,7 +295,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| color={buttonData.buttonColor as ButtonProps["color"]} | |||
| startIcon={buttonData.buttonIcon} | |||
| > | |||
| {t(buttonData.buttonText)} | |||
| {buttonData.buttonText} | |||
| </Button> | |||
| </Grid> | |||
| {/* {purchaseOrder.status.toLowerCase() === "pending" && ( | |||
| @@ -510,20 +510,20 @@ function PoInputGrid({ | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<QrCodeIcon />} | |||
| label="putaway" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleQrCode(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| // <GridActionsCellItem | |||
| // icon={<QrCodeIcon />} | |||
| // label="putaway" | |||
| // sx={{ | |||
| // color: "primary.main", | |||
| // // marginRight: 1, | |||
| // }} | |||
| // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||
| // // set _isNew to false after posting | |||
| // // or check status | |||
| // onClick={handleQrCode(params.row.id, params)} | |||
| // color="inherit" | |||
| // key="edit" | |||
| // />, | |||
| <GridActionsCellItem | |||
| icon={ | |||
| stockInLineStatusMap[status] >= 1 ? ( | |||
| @@ -237,6 +237,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| useEffect(() => { | |||
| setValue("status", "completed"); | |||
| setValue("warehouseId", options[0].value); | |||
| }, []); | |||
| useEffect(() => { | |||
| @@ -245,7 +246,16 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| clearErrors("warehouseId") | |||
| } | |||
| }, [warehouseId]); | |||
| const getWarningTextHardcode = useCallback((): string | undefined => { | |||
| const defaultWarehouseId = options[0].value | |||
| const currWarehouseId = watch("warehouseId") | |||
| if (defaultWarehouseId !== currWarehouseId) { | |||
| return t("not default warehosue") | |||
| } | |||
| return undefined | |||
| }, [options]) | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| @@ -331,7 +341,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| disableClearable | |||
| disabled | |||
| fullWidth | |||
| defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||
| defaultValue={options[0]} /// modify this later | |||
| // onChange={onChange} | |||
| getOptionLabel={(option) => option.label} | |||
| options={options} | |||
| @@ -396,6 +406,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| // value={warehouseId > 0 | |||
| // ? options.find((o) => o.value === warehouseId) | |||
| // : undefined} | |||
| defaultValue={options[0]} | |||
| // defaultValue={options.find((o) => o.value === 1)} | |||
| value={currentValue} | |||
| onChange={onChange} | |||
| getOptionLabel={(option) => option.label} | |||
| @@ -406,7 +418,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| // label={"Select warehouse"} | |||
| disabled={disabled} | |||
| error={Boolean(errors.warehouseId?.message)} | |||
| helperText={errors.warehouseId?.message} | |||
| helperText={errors.warehouseId?.message ?? | |||
| getWarningTextHardcode() | |||
| } | |||
| // helperText={warehouseHelperText} | |||
| /> | |||
| )} | |||
| @@ -1,6 +1,14 @@ | |||
| "use client"; | |||
| import { Box, Button, Grid, Modal, ModalProps, Stack, Typography } from "@mui/material"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| Grid, | |||
| Modal, | |||
| ModalProps, | |||
| Stack, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import ReactQrCodeScanner, { | |||
| ScannerConfig, | |||
| @@ -74,18 +82,18 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| ); | |||
| // QR Code Scanner | |||
| const scanner = useQcCodeScanner() | |||
| const scanner = useQcCodeScanner(); | |||
| useEffect(() => { | |||
| if (open && !scanner.isScanning) { | |||
| scanner.startScan() | |||
| scanner.startScan(); | |||
| } else if (!open && scanner.isScanning) { | |||
| scanner.stopScan() | |||
| scanner.stopScan(); | |||
| } | |||
| }, [open]) | |||
| }, [open]); | |||
| useEffect(() => { | |||
| if (scanner.values.length > 0 && !Boolean(itemDetail)) { | |||
| console.log(scanner.values[0]) | |||
| console.log(scanner.values[0]); | |||
| const data: QrCodeInfo = JSON.parse(scanner.values[0]); | |||
| console.log(data); | |||
| if (data.stockInLineId) { | |||
| @@ -93,28 +101,29 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| console.log(data.stockInLineId); | |||
| setStockInLineId(data.stockInLineId); | |||
| } | |||
| scanner.resetScan() | |||
| scanner.resetScan(); | |||
| } | |||
| }, [scanner.values]) | |||
| }, [scanner.values]); | |||
| const [itemDetail, setItemDetail] = useState<StockInLine>(); | |||
| const [disabledSubmit, setDisabledSubmit] = useState(false); | |||
| const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined) | |||
| const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined); | |||
| const fetchStockInLine = useCallback( | |||
| async (stockInLineId: number) => { | |||
| setUnavailableText(undefined) | |||
| setUnavailableText(undefined); | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| if (res.status.toLowerCase() === "received") { | |||
| console.log(res.acceptedQty) | |||
| formProps.setValue("acceptedQty", res.acceptedQty) | |||
| setDisabledSubmit(false) | |||
| console.log(res.acceptedQty); | |||
| formProps.setValue("acceptedQty", res.acceptedQty); | |||
| setDisabledSubmit(false); | |||
| setItemDetail(res); | |||
| } else if (res.status.toLowerCase() === "completed") { | |||
| setDisabledSubmit(true) | |||
| setDisabledSubmit(true); | |||
| } else { | |||
| // | |||
| setUnavailableText("Item Not Available") | |||
| setDisabledSubmit(true) | |||
| setUnavailableText("Item Not Available"); | |||
| setDisabledSubmit(true); | |||
| } | |||
| // return | |||
| }, | |||
| @@ -156,13 +165,13 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| } | |||
| return; | |||
| // return; | |||
| const res = await updateStockInLine(args); | |||
| if (Boolean(res.id)) { | |||
| // update entries | |||
| console.log(res); | |||
| // add loading | |||
| // closeHandler({}, "backdropClick"); | |||
| closeHandler({}, "backdropClick"); | |||
| } | |||
| console.log(res); | |||
| // if (res) | |||
| @@ -185,32 +194,39 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| > | |||
| <Grid container xs={12}> | |||
| <Grid item xs={12}> | |||
| { | |||
| itemDetail != undefined ? ( | |||
| unavailableText != undefined ? <Typography variant="h4" marginInlineEnd={2}>{unavailableText}</Typography> | |||
| : ( | |||
| <> | |||
| <PutawayForm itemDetail={itemDetail} warehouse={warehouse} disabled={false} /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| disabled={disabledSubmit} | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| </> | |||
| ) | |||
| {itemDetail != undefined ? ( | |||
| unavailableText != undefined ? ( | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {unavailableText} | |||
| </Typography> | |||
| ) : ( | |||
| <> | |||
| <PutawayForm | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse} | |||
| disabled={false} | |||
| /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| disabled={disabledSubmit} | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| </> | |||
| ) | |||
| : ( | |||
| // <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||
| <Typography variant="h4">{t("Will start binding procedure after scanning item qr code.")}</Typography> | |||
| ) | |||
| } | |||
| ) : ( | |||
| // <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||
| <Typography variant="h4"> | |||
| {t( | |||
| "Will start binding procedure after scanning item qr code." | |||
| )} | |||
| </Typography> | |||
| )} | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| @@ -10,10 +10,14 @@ import { EditNote } from "@mui/icons-material"; | |||
| import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||
| import QrModal from "../PoDetail/QrModal"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import NotificationIcon from '@mui/icons-material/NotificationImportant'; | |||
| import NotificationIcon from "@mui/icons-material/NotificationImportant"; | |||
| import { useSession } from "next-auth/react"; | |||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | |||
| import { fetchPoListClient } from "@/app/api/po/actions"; | |||
| import { fetchPoListClient, testing } from "@/app/api/po/actions"; | |||
| import dayjs from "dayjs"; | |||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||
| dayjs.extend(arraySupport); | |||
| type Props = { | |||
| po: PoResult[]; | |||
| @@ -24,23 +28,39 @@ type SearchQuery = Partial<Omit<PoResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| // cal offset (pageSize) | |||
| // cal limit (pageSize) | |||
| const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount }) => { | |||
| // cal limit (pageSize) | |||
| const PoSearch: React.FC<Props> = ({ | |||
| po, | |||
| warehouse, | |||
| totalCount: initTotalCount, | |||
| }) => { | |||
| const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | |||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const router = useRouter(); | |||
| const [pagingController, setPagingController] = useState(defaultPagingController) | |||
| const [totalCount, setTotalCount] = useState(initTotalCount) | |||
| const [pagingController, setPagingController] = useState( | |||
| defaultPagingController | |||
| ); | |||
| const [totalCount, setTotalCount] = useState(initTotalCount); | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | |||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | |||
| { label: t("Code"), paramName: "code", type: "text" }, | |||
| { label: t("Status"), paramName: "status", type: "select", options: ["PENDING", "RECEIVING", "COMPLETED"] }, | |||
| { label: t("Escalated"), paramName: "escalated", type: "select", options: [t("Escalated"), t("NotEscalated")] }, | |||
| { | |||
| label: t("Status"), | |||
| paramName: "status", | |||
| type: "select", | |||
| options: [t(`pending`), t(`receiving`), t(`completed`)], | |||
| }, | |||
| { | |||
| label: t("Escalated"), | |||
| paramName: "escalated", | |||
| type: "select", | |||
| options: [t("Escalated"), t("NotEscalated")], | |||
| }, | |||
| ]; | |||
| return searchCriteria; | |||
| }, [t, po]); | |||
| const onDetailClick = useCallback( | |||
| (po: PoResult) => { | |||
| router.push(`/po/edit?id=${po.id}`); | |||
| @@ -65,6 +85,11 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||
| { | |||
| name: "orderDate", | |||
| label: t("OrderDate"), | |||
| renderCell: (params) => { | |||
| return dayjs(params.orderDate) | |||
| .add(-1, "month") | |||
| .format(OUTPUT_DATE_FORMAT); | |||
| }, | |||
| }, | |||
| { | |||
| name: "supplier", | |||
| @@ -73,13 +98,18 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||
| { | |||
| name: "status", | |||
| label: t("Status"), | |||
| renderCell: (params) => { | |||
| return t(`${params.status.toLowerCase()}`); | |||
| }, | |||
| }, | |||
| { | |||
| name: "escalated", | |||
| label: t("Escalated"), | |||
| renderCell: (params) => { | |||
| return params.escalated ? <NotificationIcon color="warning"/> : undefined | |||
| } | |||
| return params.escalated ? ( | |||
| <NotificationIcon color="warning" /> | |||
| ) : undefined; | |||
| }, | |||
| }, | |||
| // { | |||
| // name: "name", | |||
| @@ -108,17 +138,30 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||
| setOpenScanner(false); | |||
| }, []); | |||
| const newPageFetch = useCallback(async (pagingController: Record<string, number>) => { | |||
| const res = await fetchPoListClient(pagingController) | |||
| if (res) { | |||
| setFilteredPo(res.records) | |||
| setTotalCount(res.total) | |||
| } | |||
| }, [fetchPoListClient, pagingController]) | |||
| const newPageFetch = useCallback( | |||
| async ( | |||
| pagingController: Record<string, number>, | |||
| filterArgs: Record<string, number> | |||
| ) => { | |||
| console.log(pagingController); | |||
| const params = { | |||
| ...pagingController, | |||
| ...filterArgs, | |||
| }; | |||
| // const res = await fetchPoListClient(params); | |||
| const res = await testing(params); | |||
| if (res) { | |||
| console.log(res.records); | |||
| setFilteredPo(res.records); | |||
| setTotalCount(res.total); | |||
| } | |||
| }, | |||
| [fetchPoListClient, pagingController] | |||
| ); | |||
| useEffect(() => { | |||
| newPageFetch(pagingController) | |||
| }, [newPageFetch, pagingController]) | |||
| newPageFetch(pagingController, filterArgs); | |||
| }, [newPageFetch, pagingController, filterArgs]); | |||
| return ( | |||
| <> | |||
| <Grid container> | |||
| @@ -127,47 +170,32 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||
| {t("Purchase Order")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid | |||
| item | |||
| xs={4} | |||
| display="flex" | |||
| justifyContent="end" | |||
| alignItems="end" | |||
| > | |||
| <QrModal | |||
| open={isOpenScanner} | |||
| onClose={onCloseScanner} | |||
| warehouse={warehouse} | |||
| /> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> | |||
| </Grid> | |||
| <> | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| setFilteredPo((prev) => | |||
| prev.filter((p) => { | |||
| return ( | |||
| p.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
| (query.status === "All" || p.status === query.status) && | |||
| (query.escalated === "All" || p.escalated === (query.escalated === t("Escalated"))) | |||
| ) | |||
| }) | |||
| ); | |||
| }} | |||
| onReset={onReset} | |||
| <Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||
| <QrModal | |||
| open={isOpenScanner} | |||
| onClose={onCloseScanner} | |||
| warehouse={warehouse} | |||
| /> | |||
| <SearchResults<PoResult> | |||
| items={filteredPo} | |||
| columns={columns} | |||
| pagingController={pagingController} | |||
| setPagingController={setPagingController} | |||
| totalCount={totalCount} | |||
| isAutoPaging={false} | |||
| /> | |||
| </> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> | |||
| </Grid> | |||
| <> | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| setFilterArgs({ ...query }); | |||
| }} | |||
| onReset={onReset} | |||
| /> | |||
| <SearchResults<PoResult> | |||
| items={filteredPo} | |||
| columns={columns} | |||
| pagingController={pagingController} | |||
| setPagingController={setPagingController} | |||
| totalCount={totalCount} | |||
| isAutoPaging={false} | |||
| /> | |||
| </> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -6,6 +6,7 @@ | |||
| "Supplier": "供應商", | |||
| "Status": "狀態", | |||
| "Escalated": "已上報", | |||
| "NotEscalated": "無上報", | |||
| "Do you want to start?": "確定開始嗎?", | |||
| "Start": "開始", | |||