@@ -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"]} | color={buttonData.buttonColor as ButtonProps["color"]} | ||||
startIcon={buttonData.buttonIcon} | startIcon={buttonData.buttonIcon} | ||||
> | > | ||||
{t(buttonData.buttonText)} | |||||
{buttonData.buttonText} | |||||
</Button> | </Button> | ||||
</Grid> | </Grid> | ||||
{/* {purchaseOrder.status.toLowerCase() === "pending" && ( | {/* {purchaseOrder.status.toLowerCase() === "pending" && ( | ||||
@@ -510,20 +510,20 @@ function PoInputGrid({ | |||||
color="inherit" | color="inherit" | ||||
key="edit" | 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 | <GridActionsCellItem | ||||
icon={ | icon={ | ||||
stockInLineStatusMap[status] >= 1 ? ( | stockInLineStatusMap[status] >= 1 ? ( | ||||
@@ -237,6 +237,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
useEffect(() => { | useEffect(() => { | ||||
setValue("status", "completed"); | setValue("status", "completed"); | ||||
setValue("warehouseId", options[0].value); | |||||
}, []); | }, []); | ||||
useEffect(() => { | useEffect(() => { | ||||
@@ -245,7 +246,16 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
clearErrors("warehouseId") | clearErrors("warehouseId") | ||||
} | } | ||||
}, [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 ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
@@ -331,7 +341,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
disableClearable | disableClearable | ||||
disabled | disabled | ||||
fullWidth | fullWidth | ||||
defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||||
defaultValue={options[0]} /// modify this later | |||||
// onChange={onChange} | // onChange={onChange} | ||||
getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
options={options} | options={options} | ||||
@@ -396,6 +406,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
// value={warehouseId > 0 | // value={warehouseId > 0 | ||||
// ? options.find((o) => o.value === warehouseId) | // ? options.find((o) => o.value === warehouseId) | ||||
// : undefined} | // : undefined} | ||||
defaultValue={options[0]} | |||||
// defaultValue={options.find((o) => o.value === 1)} | |||||
value={currentValue} | value={currentValue} | ||||
onChange={onChange} | onChange={onChange} | ||||
getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
@@ -406,7 +418,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
// label={"Select warehouse"} | // label={"Select warehouse"} | ||||
disabled={disabled} | disabled={disabled} | ||||
error={Boolean(errors.warehouseId?.message)} | error={Boolean(errors.warehouseId?.message)} | ||||
helperText={errors.warehouseId?.message} | |||||
helperText={errors.warehouseId?.message ?? | |||||
getWarningTextHardcode() | |||||
} | |||||
// helperText={warehouseHelperText} | // helperText={warehouseHelperText} | ||||
/> | /> | ||||
)} | )} | ||||
@@ -1,6 +1,14 @@ | |||||
"use client"; | "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 { useCallback, useEffect, useMemo, useState } from "react"; | ||||
import ReactQrCodeScanner, { | import ReactQrCodeScanner, { | ||||
ScannerConfig, | ScannerConfig, | ||||
@@ -74,18 +82,18 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
); | ); | ||||
// QR Code Scanner | // QR Code Scanner | ||||
const scanner = useQcCodeScanner() | |||||
const scanner = useQcCodeScanner(); | |||||
useEffect(() => { | useEffect(() => { | ||||
if (open && !scanner.isScanning) { | if (open && !scanner.isScanning) { | ||||
scanner.startScan() | |||||
scanner.startScan(); | |||||
} else if (!open && scanner.isScanning) { | } else if (!open && scanner.isScanning) { | ||||
scanner.stopScan() | |||||
scanner.stopScan(); | |||||
} | } | ||||
}, [open]) | |||||
}, [open]); | |||||
useEffect(() => { | useEffect(() => { | ||||
if (scanner.values.length > 0 && !Boolean(itemDetail)) { | 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]); | const data: QrCodeInfo = JSON.parse(scanner.values[0]); | ||||
console.log(data); | console.log(data); | ||||
if (data.stockInLineId) { | if (data.stockInLineId) { | ||||
@@ -93,28 +101,29 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
console.log(data.stockInLineId); | console.log(data.stockInLineId); | ||||
setStockInLineId(data.stockInLineId); | setStockInLineId(data.stockInLineId); | ||||
} | } | ||||
scanner.resetScan() | |||||
scanner.resetScan(); | |||||
} | } | ||||
}, [scanner.values]) | |||||
}, [scanner.values]); | |||||
const [itemDetail, setItemDetail] = useState<StockInLine>(); | const [itemDetail, setItemDetail] = useState<StockInLine>(); | ||||
const [disabledSubmit, setDisabledSubmit] = useState(false); | const [disabledSubmit, setDisabledSubmit] = useState(false); | ||||
const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined) | |||||
const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined); | |||||
const fetchStockInLine = useCallback( | const fetchStockInLine = useCallback( | ||||
async (stockInLineId: number) => { | async (stockInLineId: number) => { | ||||
setUnavailableText(undefined) | |||||
setUnavailableText(undefined); | |||||
const res = await fetchStockInLineInfo(stockInLineId); | const res = await fetchStockInLineInfo(stockInLineId); | ||||
if (res.status.toLowerCase() === "received") { | 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); | setItemDetail(res); | ||||
} else if (res.status.toLowerCase() === "completed") { | } else if (res.status.toLowerCase() === "completed") { | ||||
setDisabledSubmit(true) | |||||
setDisabledSubmit(true); | |||||
} else { | } else { | ||||
// | // | ||||
setUnavailableText("Item Not Available") | |||||
setDisabledSubmit(true) | |||||
setUnavailableText("Item Not Available"); | |||||
setDisabledSubmit(true); | |||||
} | } | ||||
// return | // return | ||||
}, | }, | ||||
@@ -156,13 +165,13 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
return false; | return false; | ||||
} | } | ||||
return; | |||||
// return; | |||||
const res = await updateStockInLine(args); | const res = await updateStockInLine(args); | ||||
if (Boolean(res.id)) { | if (Boolean(res.id)) { | ||||
// update entries | // update entries | ||||
console.log(res); | console.log(res); | ||||
// add loading | // add loading | ||||
// closeHandler({}, "backdropClick"); | |||||
closeHandler({}, "backdropClick"); | |||||
} | } | ||||
console.log(res); | console.log(res); | ||||
// if (res) | // if (res) | ||||
@@ -185,32 +194,39 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
> | > | ||||
<Grid container xs={12}> | <Grid container xs={12}> | ||||
<Grid item 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> | ||||
</Grid> | </Grid> | ||||
</Box> | </Box> | ||||
@@ -10,10 +10,14 @@ import { EditNote } from "@mui/icons-material"; | |||||
import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | ||||
import QrModal from "../PoDetail/QrModal"; | import QrModal from "../PoDetail/QrModal"; | ||||
import { WarehouseResult } from "@/app/api/warehouse"; | 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 { useSession } from "next-auth/react"; | ||||
import { defaultPagingController } from "../SearchResults/SearchResults"; | 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 = { | type Props = { | ||||
po: PoResult[]; | po: PoResult[]; | ||||
@@ -24,23 +28,39 @@ type SearchQuery = Partial<Omit<PoResult, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
// cal offset (pageSize) | // 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 [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | ||||
const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||||
const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
const router = useRouter(); | 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(() => { | const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | ||||
var searchCriteria: Criterion<SearchParamNames>[] = [ | var searchCriteria: Criterion<SearchParamNames>[] = [ | ||||
{ label: t("Code"), paramName: "code", type: "text" }, | { 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; | return searchCriteria; | ||||
}, [t, po]); | }, [t, po]); | ||||
const onDetailClick = useCallback( | const onDetailClick = useCallback( | ||||
(po: PoResult) => { | (po: PoResult) => { | ||||
router.push(`/po/edit?id=${po.id}`); | router.push(`/po/edit?id=${po.id}`); | ||||
@@ -65,6 +85,11 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
{ | { | ||||
name: "orderDate", | name: "orderDate", | ||||
label: t("OrderDate"), | label: t("OrderDate"), | ||||
renderCell: (params) => { | |||||
return dayjs(params.orderDate) | |||||
.add(-1, "month") | |||||
.format(OUTPUT_DATE_FORMAT); | |||||
}, | |||||
}, | }, | ||||
{ | { | ||||
name: "supplier", | name: "supplier", | ||||
@@ -73,13 +98,18 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
{ | { | ||||
name: "status", | name: "status", | ||||
label: t("Status"), | label: t("Status"), | ||||
renderCell: (params) => { | |||||
return t(`${params.status.toLowerCase()}`); | |||||
}, | |||||
}, | }, | ||||
{ | { | ||||
name: "escalated", | name: "escalated", | ||||
label: t("Escalated"), | label: t("Escalated"), | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return params.escalated ? <NotificationIcon color="warning"/> : undefined | |||||
} | |||||
return params.escalated ? ( | |||||
<NotificationIcon color="warning" /> | |||||
) : undefined; | |||||
}, | |||||
}, | }, | ||||
// { | // { | ||||
// name: "name", | // name: "name", | ||||
@@ -108,17 +138,30 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
setOpenScanner(false); | 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(() => { | useEffect(() => { | ||||
newPageFetch(pagingController) | |||||
}, [newPageFetch, pagingController]) | |||||
newPageFetch(pagingController, filterArgs); | |||||
}, [newPageFetch, pagingController, filterArgs]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<Grid container> | <Grid container> | ||||
@@ -127,47 +170,32 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
{t("Purchase Order")} | {t("Purchase Order")} | ||||
</Typography> | </Typography> | ||||
</Grid> | </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": "供應商", | "Supplier": "供應商", | ||||
"Status": "狀態", | "Status": "狀態", | ||||
"Escalated": "已上報", | "Escalated": "已上報", | ||||
"NotEscalated": "無上報", | |||||
"Do you want to start?": "確定開始嗎?", | "Do you want to start?": "確定開始嗎?", | ||||
"Start": "開始", | "Start": "開始", | ||||