@@ -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": "開始", | |||