@@ -14,8 +14,8 @@ export const metadata: Metadata = { | |||||
title: "Purchase Order", | title: "Purchase Order", | ||||
}; | }; | ||||
const production: React.FC = async () => { | |||||
const { t } = await getServerI18n("claims"); | |||||
const PurchaseOrder: React.FC = async () => { | |||||
const { t } = await getServerI18n("purchaseOrder"); | |||||
// preloadClaims(); | // preloadClaims(); | ||||
return ( | return ( | ||||
@@ -45,4 +45,4 @@ const production: React.FC = async () => { | |||||
); | ); | ||||
}; | }; | ||||
export default production; | |||||
export default PurchaseOrder; |
@@ -15,7 +15,8 @@ export interface PostStockInLiineResponse<T> { | |||||
type?: string | type?: string | ||||
message: string | null; | message: string | null; | ||||
errorPosition: string | keyof T; | errorPosition: string | keyof T; | ||||
entity: StockInLine | StockInLine[] | |||||
entity: T | T[] | |||||
// entity: StockInLine | StockInLine[] | |||||
} | } | ||||
export interface StockInLineEntry { | export interface StockInLineEntry { | ||||
@@ -83,6 +84,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||||
body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}); | }); | ||||
// revalidateTag("po"); | |||||
return stockInLine | return stockInLine | ||||
} | } | ||||
@@ -92,16 +94,34 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) | |||||
body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}); | }); | ||||
// revalidateTag("po"); | |||||
return stockInLine | return stockInLine | ||||
} | } | ||||
export const startPo = async (poId: number) => { | |||||
const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/start/${poId}`, { | |||||
method: "POST", | |||||
body: JSON.stringify({ poId }), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
revalidateTag("po"); | |||||
return po | |||||
} | |||||
export const checkPolAndCompletePo = async (poId: number) => { | export const checkPolAndCompletePo = async (poId: number) => { | ||||
const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po//check/${poId}`, { | |||||
const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/check/${poId}`, { | |||||
method: "POST", | method: "POST", | ||||
body: JSON.stringify({ poId }), | body: JSON.stringify({ poId }), | ||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}); | }); | ||||
revalidateTag("po"); | |||||
return po | return po | ||||
} | } | ||||
export const fetchPoInClient = cache(async (id: number) => { | |||||
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||||
next: { tags: ["po"] }, | |||||
}); | |||||
}); | |||||
@@ -41,8 +41,10 @@ export interface StockInLine { | |||||
acceptedQty: number | acceptedQty: number | ||||
price: number | price: number | ||||
priceUnit: string | priceUnit: string | ||||
productionDate: string | |||||
expiryDate: string | |||||
shelfLife?: number, | |||||
receiptDate?: string | |||||
productionDate?: string | |||||
expiryDate?: string | |||||
status: string | status: string | ||||
supplier: string | supplier: string | ||||
lotNo: string | lotNo: string | ||||
@@ -18,6 +18,12 @@ export const integerFormatter = new Intl.NumberFormat("en-HK", { | |||||
}) | }) | ||||
export const INPUT_DATE_FORMAT = "YYYY-MM-DD"; | |||||
export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD"; | |||||
export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | |||||
export const stockInLineStatusMap: { [status: string]: number } = { | export const stockInLineStatusMap: { [status: string]: number } = { | ||||
"draft": 0, | "draft": 0, | ||||
"pending": 1, | "pending": 1, | ||||
@@ -36,8 +36,10 @@ import { | |||||
} from "@mui/x-data-grid"; | } from "@mui/x-data-grid"; | ||||
import { | import { | ||||
checkPolAndCompletePo, | checkPolAndCompletePo, | ||||
fetchPoInClient, | |||||
fetchStockInLineInfo, | fetchStockInLineInfo, | ||||
PurchaseQcResult, | PurchaseQcResult, | ||||
startPo, | |||||
testFetch, | testFetch, | ||||
} from "@/app/api/po/actions"; | } from "@/app/api/po/actions"; | ||||
import { | import { | ||||
@@ -88,14 +90,46 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
console.log(cameras); | console.log(cameras); | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | |||||
const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | |||||
const [rows, setRows] = useState<PurchaseOrderLine[]>( | |||||
purchaseOrder.pol || [] | |||||
); | |||||
const params = useSearchParams(); | const params = useSearchParams(); | ||||
const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status); | |||||
const handleComplete = useCallback(async () => { | |||||
const checkRes = await checkPolAndCompletePo(purchaseOrder.id); | |||||
console.log(checkRes); | |||||
const newPo = await fetchPoInClient(purchaseOrder.id); | |||||
setPurchaseOrder(newPo); | |||||
}, [checkPolAndCompletePo, fetchPoInClient]); | |||||
const handleStartPo = useCallback(async () => { | |||||
const startRes = await startPo(purchaseOrder.id); | |||||
console.log(startRes); | |||||
const newPo = await fetchPoInClient(purchaseOrder.id); | |||||
setPurchaseOrder(newPo); | |||||
}, [startPo, fetchPoInClient]); | |||||
useEffect(() => { | |||||
setRows(purchaseOrder.pol || []); | |||||
}, [purchaseOrder]); | |||||
function Row(props: { row: PurchaseOrderLine }) { | function Row(props: { row: PurchaseOrderLine }) { | ||||
const { row } = props; | const { row } = props; | ||||
const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
const [processedQty, setProcessedQty] = useState(row.processed); | const [processedQty, setProcessedQty] = useState(row.processed); | ||||
const [currStatus, setCurrStatus] = useState(row.status); | const [currStatus, setCurrStatus] = useState(row.status); | ||||
const [stockInLine, setStockInLine] = useState(row.stockInLine); | |||||
const totalWeight = useMemo( | |||||
() => calculateWeight(row.qty, row.uom), | |||||
[calculateWeight] | |||||
); | |||||
const weightUnit = useMemo( | |||||
() => returnWeightUnit(row.uom), | |||||
[returnWeightUnit] | |||||
); | |||||
useEffect(() => { | useEffect(() => { | ||||
if (processedQty === row.qty) { | if (processedQty === row.qty) { | ||||
setCurrStatus("completed".toUpperCase()); | setCurrStatus("completed".toUpperCase()); | ||||
@@ -106,20 +140,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
} | } | ||||
}, [processedQty]); | }, [processedQty]); | ||||
const totalWeight = useMemo( | |||||
() => calculateWeight(row.qty, row.uom), | |||||
[calculateWeight] | |||||
); | |||||
const weightUnit = useMemo( | |||||
() => returnWeightUnit(row.uom), | |||||
[returnWeightUnit] | |||||
); | |||||
return ( | return ( | ||||
<> | <> | ||||
<TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | ||||
<TableCell> | <TableCell> | ||||
<IconButton | <IconButton | ||||
disabled={purchaseOrder.status.toLowerCase() === "pending"} | |||||
aria-label="expand row" | aria-label="expand row" | ||||
size="small" | size="small" | ||||
onClick={() => setOpen(!open)} | onClick={() => setOpen(!open)} | ||||
@@ -152,9 +178,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
<PoInputGrid | <PoInputGrid | ||||
qc={qc} | qc={qc} | ||||
setRows={setRows} | setRows={setRows} | ||||
stockInLine={stockInLine} | |||||
setStockInLine={setStockInLine} | |||||
setProcessedQty={setProcessedQty} | setProcessedQty={setProcessedQty} | ||||
itemDetail={row} | itemDetail={row} | ||||
stockInLine={row.stockInLine} | |||||
warehouse={warehouse} | warehouse={warehouse} | ||||
/> | /> | ||||
</Box> | </Box> | ||||
@@ -199,20 +226,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
setPutAwayOpen(true); | setPutAwayOpen(true); | ||||
}, []); | }, []); | ||||
const handleComplete = useCallback(async () => { | |||||
const res = await checkPolAndCompletePo(po.id) | |||||
if (res.type === "completed") { | |||||
// toast.success(res.message) | |||||
console.log(res) | |||||
return | |||||
} | |||||
if (res.type === "receiving") { | |||||
// toast.error(res.message) | |||||
console.log(res) | |||||
return | |||||
} | |||||
}, [checkPolAndCompletePo]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack | <Stack | ||||
@@ -220,13 +233,26 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
// component="form" | // component="form" | ||||
// onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
> | > | ||||
<Grid container xs={12} justifyContent="space-between"> | |||||
<Grid container xs={12} justifyContent="start"> | |||||
<Grid item> | <Grid item> | ||||
<Typography mb={2} variant="h4"> | <Typography mb={2} variant="h4"> | ||||
{po.code} | |||||
{/* {purchaseOrder.code} - {currPoStatus} */} | |||||
{purchaseOrder.code} - {purchaseOrder.status} | |||||
</Typography> | </Typography> | ||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
<Grid container xs={12} justifyContent="start"> | |||||
{purchaseOrder.status.toLowerCase() === "pending" && ( | |||||
<Grid item> | |||||
<Button onClick={handleStartPo}>Start</Button> | |||||
</Grid> | |||||
)} | |||||
{purchaseOrder.status.toLowerCase() === "receiving" && ( | |||||
<Grid item> | |||||
<Button onClick={handleComplete}>Complete</Button> | |||||
</Grid> | |||||
)} | |||||
</Grid> | |||||
{/* <Grid container xs={12} justifyContent="space-between"> | {/* <Grid container xs={12} justifyContent="space-between"> | ||||
<Button onClick={handleComplete}>Complete</Button> | <Button onClick={handleComplete}>Complete</Button> | ||||
</Grid> */} | </Grid> */} | ||||
@@ -242,9 +268,15 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
</Tabs> | </Tabs> | ||||
</Grid> | </Grid> | ||||
{/* <Grid item xs={4}> */} | {/* <Grid item xs={4}> */} | ||||
{/* scanner */} | |||||
{/* scanner */} | |||||
{/* </Grid> */} | {/* </Grid> */} | ||||
<Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||||
<Grid | |||||
item | |||||
xs={4} | |||||
display="flex" | |||||
justifyContent="end" | |||||
alignItems="end" | |||||
> | |||||
<QrModal | <QrModal | ||||
open={isOpenScanner} | open={isOpenScanner} | ||||
onClose={onCloseScanner} | onClose={onCloseScanner} | ||||
@@ -63,6 +63,7 @@ interface ResultWithId { | |||||
interface Props { | interface Props { | ||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | ||||
setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | |||||
setProcessedQty: Dispatch<SetStateAction<number>>; | setProcessedQty: Dispatch<SetStateAction<number>>; | ||||
itemDetail: PurchaseOrderLine; | itemDetail: PurchaseOrderLine; | ||||
stockInLine: StockInLine[]; | stockInLine: StockInLine[]; | ||||
@@ -100,6 +101,7 @@ class ProcessRowUpdateError extends Error { | |||||
function PoInputGrid({ | function PoInputGrid({ | ||||
qc, | qc, | ||||
setRows, | setRows, | ||||
setStockInLine, | |||||
setProcessedQty, | setProcessedQty, | ||||
itemDetail, | itemDetail, | ||||
stockInLine, | stockInLine, | ||||
@@ -122,6 +124,7 @@ function PoInputGrid({ | |||||
const [escalOpen, setEscalOpen] = useState(false); | const [escalOpen, setEscalOpen] = useState(false); | ||||
const [stockInOpen, setStockInOpen] = useState(false); | const [stockInOpen, setStockInOpen] = useState(false); | ||||
const [putAwayOpen, setPutAwayOpen] = useState(false); | const [putAwayOpen, setPutAwayOpen] = useState(false); | ||||
const [btnIsLoading, setBtnIsLoading] = useState(false); | |||||
const [currQty, setCurrQty] = useState(() => { | const [currQty, setCurrQty] = useState(() => { | ||||
const total = entries.reduce( | const total = entries.reduce( | ||||
(acc, curr) => acc + (curr.acceptedQty || 0), | (acc, curr) => acc + (curr.acceptedQty || 0), | ||||
@@ -147,17 +150,14 @@ function PoInputGrid({ | |||||
); | ); | ||||
const handleStart = useCallback( | const handleStart = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
setBtnIsLoading(true); | |||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
})); | })); | ||||
setTimeout(async () => { | setTimeout(async () => { | ||||
// post stock in line | // post stock in line | ||||
console.log("delayed"); | |||||
console.log(params); | |||||
const oldId = params.row.id; | const oldId = params.row.id; | ||||
console.log(oldId); | |||||
console.log(params.row); | |||||
const postData = { | const postData = { | ||||
itemId: params.row.itemId, | itemId: params.row.itemId, | ||||
itemNo: params.row.itemNo, | itemNo: params.row.itemNo, | ||||
@@ -171,6 +171,13 @@ function PoInputGrid({ | |||||
setEntries((prev) => | setEntries((prev) => | ||||
prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)) | prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)) | ||||
); | ); | ||||
setStockInLine( | |||||
(prev) => | |||||
prev.map((p) => | |||||
p.id === oldId ? (res.entity as StockInLine) : p | |||||
) as StockInLine[] | |||||
); | |||||
setBtnIsLoading(false); | |||||
// do post directly to test | // do post directly to test | ||||
// openStartModal(); | // openStartModal(); | ||||
}, 200); | }, 200); | ||||
@@ -183,6 +190,7 @@ function PoInputGrid({ | |||||
const handleQC = useCallback( | const handleQC = useCallback( | ||||
(id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
setBtnIsLoading(true); | |||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
@@ -199,12 +207,14 @@ function PoInputGrid({ | |||||
// open qc modal | // open qc modal | ||||
console.log("delayed"); | console.log("delayed"); | ||||
openQcModal(); | openQcModal(); | ||||
setBtnIsLoading(false); | |||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[fetchQcDefaultValue] | [fetchQcDefaultValue] | ||||
); | ); | ||||
const handleEscalation = useCallback( | const handleEscalation = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
// setBtnIsLoading(true); | |||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
@@ -214,12 +224,14 @@ function PoInputGrid({ | |||||
// open qc modal | // open qc modal | ||||
console.log("delayed"); | console.log("delayed"); | ||||
openEscalationModal(); | openEscalationModal(); | ||||
// setBtnIsLoading(false); | |||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | [] | ||||
); | ); | ||||
const handleStockIn = useCallback( | const handleStockIn = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
// setBtnIsLoading(true); | |||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
@@ -231,6 +243,7 @@ function PoInputGrid({ | |||||
// return the record with its status as pending | // return the record with its status as pending | ||||
// update layout | // update layout | ||||
console.log("delayed"); | console.log("delayed"); | ||||
// setBtnIsLoading(false); | |||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | [] | ||||
@@ -238,6 +251,7 @@ function PoInputGrid({ | |||||
const handlePutAway = useCallback( | const handlePutAway = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
// setBtnIsLoading(true); | |||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
@@ -249,6 +263,7 @@ function PoInputGrid({ | |||||
// return the record with its status as pending | // return the record with its status as pending | ||||
// update layout | // update layout | ||||
console.log("delayed"); | console.log("delayed"); | ||||
// setBtnIsLoading(false); | |||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | [] | ||||
@@ -256,6 +271,7 @@ function PoInputGrid({ | |||||
const printQrcode = useCallback( | const printQrcode = useCallback( | ||||
async (row: any) => { | async (row: any) => { | ||||
setBtnIsLoading(true); | |||||
console.log(row.id); | console.log(row.id); | ||||
const postData = { stockInLineIds: [row.id] }; | const postData = { stockInLineIds: [row.id] }; | ||||
// const postData = { stockInLineIds: [42,43,44] }; | // const postData = { stockInLineIds: [42,43,44] }; | ||||
@@ -264,6 +280,7 @@ function PoInputGrid({ | |||||
console.log(response); | console.log(response); | ||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!); | downloadFile(new Uint8Array(response.blobValue), response.filename!!); | ||||
} | } | ||||
setBtnIsLoading(false); | |||||
}, | }, | ||||
[fetchPoQrcode, downloadFile] | [fetchPoQrcode, downloadFile] | ||||
); | ); | ||||
@@ -319,11 +336,11 @@ function PoInputGrid({ | |||||
() => [ | () => [ | ||||
{ | { | ||||
field: "itemNo", | field: "itemNo", | ||||
flex: 0.8, | |||||
flex: 0.4, | |||||
}, | }, | ||||
{ | { | ||||
field: "itemName", | field: "itemName", | ||||
flex: 1, | |||||
flex: 0.6, | |||||
}, | }, | ||||
{ | { | ||||
field: "acceptedQty", | field: "acceptedQty", | ||||
@@ -363,7 +380,7 @@ function PoInputGrid({ | |||||
field: "actions", | field: "actions", | ||||
type: "actions", | type: "actions", | ||||
headerName: "start | qc | escalation | stock in | putaway | delete", | headerName: "start | qc | escalation | stock in | putaway | delete", | ||||
flex: 1, | |||||
flex: 1.5, | |||||
cellClassName: "actions", | cellClassName: "actions", | ||||
getActions: (params) => { | getActions: (params) => { | ||||
console.log(params.row.status); | console.log(params.row.status); | ||||
@@ -376,7 +393,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={!(stockInLineStatusMap[status] === 0)} | |||||
disabled={btnIsLoading || !(stockInLineStatusMap[status] === 0)} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleStart(params.row.id, params)} | onClick={handleStart(params.row.id, params)} | ||||
@@ -390,7 +407,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] < 1} | |||||
disabled={btnIsLoading || stockInLineStatusMap[status] < 1} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleQC(params.row.id, params)} | onClick={handleQC(params.row.id, params)} | ||||
@@ -405,8 +422,9 @@ function PoInputGrid({ | |||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={ | disabled={ | ||||
btnIsLoading || | |||||
stockInLineStatusMap[status] <= 0 || | stockInLineStatusMap[status] <= 0 || | ||||
stockInLineStatusMap[status] >= 4 | |||||
stockInLineStatusMap[status] >= 5 | |||||
} | } | ||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
@@ -421,7 +439,11 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} | |||||
disabled={ | |||||
btnIsLoading || | |||||
stockInLineStatusMap[status] <= 2 || | |||||
stockInLineStatusMap[status] >= 7 | |||||
} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleStockIn(params.row.id, params)} | onClick={handleStockIn(params.row.id, params)} | ||||
@@ -435,7 +457,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] < 7} | |||||
disabled={btnIsLoading || stockInLineStatusMap[status] < 7} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handlePutAway(params.row.id, params)} | onClick={handlePutAway(params.row.id, params)} | ||||
@@ -449,7 +471,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] !== 8} | |||||
disabled={btnIsLoading || stockInLineStatusMap[status] !== 8} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleQrCode(params.row.id, params)} | onClick={handleQrCode(params.row.id, params)} | ||||
@@ -462,7 +484,7 @@ function PoInputGrid({ | |||||
sx={{ | sx={{ | ||||
color: "error.main", | color: "error.main", | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] !== 0} | |||||
disabled={btnIsLoading || stockInLineStatusMap[status] !== 0} | |||||
// disabled={Boolean(params.row.status)} | // disabled={Boolean(params.row.status)} | ||||
onClick={handleDelete(params.row.id)} | onClick={handleDelete(params.row.id)} | ||||
color="inherit" | color="inherit" | ||||
@@ -472,7 +494,7 @@ function PoInputGrid({ | |||||
}, | }, | ||||
}, | }, | ||||
], | ], | ||||
[] | |||||
[btnIsLoading] | |||||
); | ); | ||||
const addRow = useCallback(() => { | const addRow = useCallback(() => { | ||||
@@ -533,6 +555,8 @@ function PoInputGrid({ | |||||
const newEntries = entries.map((e) => | const newEntries = entries.map((e) => | ||||
getRowId(e) === getRowId(originalRow) ? rowToSave : e | getRowId(e) === getRowId(originalRow) ? rowToSave : e | ||||
); | ); | ||||
setStockInLine(newEntries as StockInLine[]); | |||||
console.log("triggered"); | |||||
setEntries(newEntries); | setEntries(newEntries); | ||||
//update remaining qty | //update remaining qty | ||||
const total = newEntries.reduce( | const total = newEntries.reduce( | ||||
@@ -569,9 +593,7 @@ function PoInputGrid({ | |||||
</Button> | </Button> | ||||
</Box> | </Box> | ||||
); | ); | ||||
useEffect(() => { | |||||
console.log(modalInfo); | |||||
}, [modalInfo]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<StyledDataGrid | <StyledDataGrid | ||||
@@ -623,7 +645,9 @@ function PoInputGrid({ | |||||
<> | <> | ||||
<PoQcStockInModal | <PoQcStockInModal | ||||
type={"qc"} | type={"qc"} | ||||
// setRows={setRows} | |||||
setEntries={setEntries} | setEntries={setEntries} | ||||
setStockInLine={setStockInLine} | |||||
setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
qc={qc} | qc={qc} | ||||
open={qcOpen} | open={qcOpen} | ||||
@@ -636,7 +660,9 @@ function PoInputGrid({ | |||||
<> | <> | ||||
<PoQcStockInModal | <PoQcStockInModal | ||||
type={"escalation"} | type={"escalation"} | ||||
// setRows={setRows} | |||||
setEntries={setEntries} | setEntries={setEntries} | ||||
setStockInLine={setStockInLine} | |||||
setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
// qc={qc} | // qc={qc} | ||||
open={escalOpen} | open={escalOpen} | ||||
@@ -649,7 +675,9 @@ function PoInputGrid({ | |||||
<> | <> | ||||
<PoQcStockInModal | <PoQcStockInModal | ||||
type={"stockIn"} | type={"stockIn"} | ||||
// setRows={setRows} | |||||
setEntries={setEntries} | setEntries={setEntries} | ||||
setStockInLine={setStockInLine} | |||||
// qc={qc} | // qc={qc} | ||||
setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
open={stockInOpen} | open={stockInOpen} | ||||
@@ -662,7 +690,9 @@ function PoInputGrid({ | |||||
<> | <> | ||||
<PoQcStockInModal | <PoQcStockInModal | ||||
type={"putaway"} | type={"putaway"} | ||||
// setRows={setRows} | |||||
setEntries={setEntries} | setEntries={setEntries} | ||||
setStockInLine={setStockInLine} | |||||
setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
open={putAwayOpen} | open={putAwayOpen} | ||||
warehouse={warehouse} | warehouse={warehouse} | ||||
@@ -22,23 +22,35 @@ import { useTranslation } from "react-i18next"; | |||||
import QcForm from "./QcForm"; | import QcForm from "./QcForm"; | ||||
import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material"; | import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material"; | ||||
import { StockInLine } from "@/app/api/po"; | |||||
import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
import EscalationForm from "./EscalationForm"; | import EscalationForm from "./EscalationForm"; | ||||
import StockInForm from "./StockInForm"; | import StockInForm from "./StockInForm"; | ||||
import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
import { | |||||
INPUT_DATE_FORMAT, | |||||
stockInLineStatusMap, | |||||
} from "@/app/utils/formatUtil"; | |||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import arraySupport from "dayjs/plugin/arraySupport"; | import arraySupport from "dayjs/plugin/arraySupport"; | ||||
import { downloadFile } from "@/app/utils/commonUtil"; | |||||
import { fetchPoQrcode } from "@/app/api/pdf/actions"; | |||||
dayjs.extend(arraySupport) | |||||
dayjs.extend(arraySupport); | |||||
interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||||
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | ||||
setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | |||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | ||||
setItemDetail: Dispatch<SetStateAction<(StockInLine & { | |||||
warehouseId?: number; | |||||
}) | undefined>> | |||||
setItemDetail: Dispatch< | |||||
SetStateAction< | |||||
| (StockInLine & { | |||||
warehouseId?: number; | |||||
}) | |||||
| undefined | |||||
> | |||||
>; | |||||
qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
warehouse?: any[]; | warehouse?: any[]; | ||||
type: "qc" | "stockIn" | "escalation" | "putaway"; | type: "qc" | "stockIn" | "escalation" | "putaway"; | ||||
@@ -75,7 +87,9 @@ const style = { | |||||
const PoQcStockInModal: React.FC<Props> = ({ | const PoQcStockInModal: React.FC<Props> = ({ | ||||
type, | type, | ||||
// setRows, | |||||
setEntries, | setEntries, | ||||
setStockInLine, | |||||
open, | open, | ||||
onClose, | onClose, | ||||
itemDetail, | itemDetail, | ||||
@@ -86,12 +100,16 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const params = useSearchParams(); | const params = useSearchParams(); | ||||
const [btnIsLoading, setBtnIsLoading] = useState(false); | |||||
console.log(params.get("id")); | console.log(params.get("id")); | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
console.log(itemDetail.qcResult); | console.log(itemDetail.qcResult); | ||||
const formProps = useForm<ModalFormInput>({ | const formProps = useForm<ModalFormInput>({ | ||||
defaultValues: { | defaultValues: { | ||||
...itemDetail, | ...itemDetail, | ||||
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||||
// warehouseId: itemDetail.defaultWarehouseId || 0 | |||||
}, | }, | ||||
}); | }); | ||||
// console.log(formProps); | // console.log(formProps); | ||||
@@ -111,40 +129,95 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
} | } | ||||
}, [itemDetail]); | }, [itemDetail]); | ||||
const fix0IndexedDate = useCallback((date: string | number[] | undefined) => { | |||||
if (Array.isArray(date)) { | |||||
console.log(date) | |||||
return dayjs([date[0], date[1] - 1, date[2]]).format("YYYY-MM-DD") | |||||
} | |||||
return date | |||||
}, []) | |||||
// const fix0IndexedDate = useCallback((date: string | number[] | undefined) => { | |||||
// if (Array.isArray(date)) { | |||||
// console.log(date); | |||||
// return dayjs([date[0], date[1] - 1, date[2]]).format("YYYY-MM-DD"); | |||||
// } | |||||
// return date; | |||||
// }, []); | |||||
const checkStockIn = useCallback( | |||||
(data: ModalFormInput): boolean => { | |||||
let hasErrors = false; | |||||
if (itemDetail.shelfLife && !data.productionDate && !data.expiryDate) { | |||||
formProps.setError("productionDate", { | |||||
message: "Please provide at least one", | |||||
type: "invalid", | |||||
}); | |||||
formProps.setError("expiryDate", { | |||||
message: "Please provide at least one", | |||||
type: "invalid", | |||||
}); | |||||
hasErrors = true; | |||||
} | |||||
if (!itemDetail.shelfLife && !data.expiryDate) { | |||||
formProps.setError("expiryDate", { | |||||
message: "Please provide expiry date", | |||||
type: "invalid", | |||||
}); | |||||
hasErrors = true; | |||||
} | |||||
if (data.expiryDate && data.expiryDate < data.receiptDate!!) { | |||||
formProps.setError("expiryDate", { | |||||
message: "Expired", | |||||
type: "invalid", | |||||
}); | |||||
hasErrors = true; | |||||
} | |||||
return hasErrors; | |||||
}, | |||||
[itemDetail, formProps] | |||||
); | |||||
const checkPutaway = useCallback( | |||||
(data: ModalFormInput): boolean => { | |||||
let hasErrors = false; | |||||
console.log(data.warehouseId); | |||||
if (!data.warehouseId || data.warehouseId <= 0) { | |||||
formProps.setError("warehouseId", { | |||||
message: "Please provide warehouseId", | |||||
type: "invalid", | |||||
}); | |||||
hasErrors = true; | |||||
} | |||||
return hasErrors; | |||||
}, | |||||
[itemDetail, formProps] | |||||
); | |||||
const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | ||||
async (data, event) => { | async (data, event) => { | ||||
formProps.clearErrors(); | |||||
let hasErrors = false; | let hasErrors = false; | ||||
setBtnIsLoading(true); | |||||
console.log(errors); | console.log(errors); | ||||
console.log(data); | console.log(data); | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
console.log(data.receiptDate) | |||||
console.log(fix0IndexedDate(data.receiptDate)) | |||||
// console.log(fix0IndexedDate(data.receiptDate)); | |||||
try { | try { | ||||
// add checking | // add checking | ||||
// const qty = data.sampleRate | |||||
if (type === "stockIn") { | |||||
hasErrors = checkStockIn(data) | |||||
console.log(hasErrors) | |||||
} | |||||
if (type === "putaway") { | |||||
hasErrors = checkPutaway(data); | |||||
console.log(hasErrors) | |||||
} | |||||
//////////////////////// modify this mess later ////////////////////// | //////////////////////// modify this mess later ////////////////////// | ||||
var productionDate = null; | var productionDate = null; | ||||
var expiryDate = null; | var expiryDate = null; | ||||
var receiptDate = null; | var receiptDate = null; | ||||
var acceptedQty = null; | var acceptedQty = null; | ||||
if (data.productionDate && data.productionDate.length > 0) { | |||||
productionDate = fix0IndexedDate(data.productionDate); | |||||
if (data.productionDate) { | |||||
productionDate = dayjs(data.productionDate).format(INPUT_DATE_FORMAT); | |||||
} | } | ||||
if (data.expiryDate && data.expiryDate.length > 0) { | |||||
expiryDate = fix0IndexedDate(data.expiryDate); | |||||
if (data.expiryDate) { | |||||
expiryDate = dayjs(data.expiryDate).format(INPUT_DATE_FORMAT); | |||||
} | } | ||||
if (data.receiptDate && data.receiptDate.length > 0) { | |||||
receiptDate = fix0IndexedDate(data.receiptDate); | |||||
if (data.receiptDate) { | |||||
receiptDate = dayjs(data.receiptDate).format(INPUT_DATE_FORMAT); | |||||
} | } | ||||
// if () | // if () | ||||
if (data.qcResult) { | if (data.qcResult) { | ||||
@@ -163,17 +236,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
receiptDate: receiptDate, | receiptDate: receiptDate, | ||||
} as StockInLineEntry & ModalFormInput; | } as StockInLineEntry & ModalFormInput; | ||||
////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
console.log(args); | |||||
// return | |||||
if (hasErrors) { | if (hasErrors) { | ||||
console.log(args); | |||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
return false; | |||||
setBtnIsLoading(false); | |||||
return; | |||||
} | } | ||||
console.log(args); | |||||
// setBtnIsLoading(false); | |||||
// return | |||||
const res = await updateStockInLine(args); | const res = await updateStockInLine(args); | ||||
if (Boolean(res.id)) { | if (Boolean(res.id)) { | ||||
// update entries | // update entries | ||||
const newEntries = res.entity as StockInLine[]; | const newEntries = res.entity as StockInLine[]; | ||||
console.log(newEntries) | |||||
console.log(newEntries); | |||||
if (setEntries) { | if (setEntries) { | ||||
setEntries((prev) => { | setEntries((prev) => { | ||||
const updatedEntries = [...prev]; // Create a new array | const updatedEntries = [...prev]; // Create a new array | ||||
@@ -181,7 +257,24 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
const index = updatedEntries.findIndex((p) => p.id === item.id); | const index = updatedEntries.findIndex((p) => p.id === item.id); | ||||
if (index !== -1) { | if (index !== -1) { | ||||
// Update existing item | // Update existing item | ||||
console.log(item) | |||||
console.log(item); | |||||
updatedEntries[index] = item; | |||||
} else { | |||||
// Add new item | |||||
updatedEntries.push(item); | |||||
} | |||||
}); | |||||
return updatedEntries; // Return the new array | |||||
}); | |||||
} | |||||
if (setStockInLine) { | |||||
setStockInLine((prev) => { | |||||
const updatedEntries = [...prev]; // Create a new array | |||||
newEntries.forEach((item) => { | |||||
const index = updatedEntries.findIndex((p) => p.id === item.id); | |||||
if (index !== -1) { | |||||
// Update existing item | |||||
console.log(item); | |||||
updatedEntries[index] = item; | updatedEntries[index] = item; | ||||
} else { | } else { | ||||
// Add new item | // Add new item | ||||
@@ -192,31 +285,56 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
}); | }); | ||||
} | } | ||||
// add loading | // add loading | ||||
setItemDetail(undefined) | |||||
setBtnIsLoading(false); | |||||
setItemDetail(undefined); | |||||
closeHandler({}, "backdropClick"); | closeHandler({}, "backdropClick"); | ||||
} | } | ||||
console.log(res); | console.log(res); | ||||
// if (res) | // if (res) | ||||
} catch (e) { | } catch (e) { | ||||
// server error | // server error | ||||
setBtnIsLoading(false); | |||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
console.log(e); | console.log(e); | ||||
} | } | ||||
}, | }, | ||||
[t, itemDetail] | |||||
[t, itemDetail, checkStockIn, checkPutaway] | |||||
); | ); | ||||
const printQrcode = useCallback(async () => { | |||||
setBtnIsLoading(true); | |||||
const postData = { stockInLineIds: [itemDetail.id] }; | |||||
// const postData = { stockInLineIds: [42,43,44] }; | |||||
const response = await fetchPoQrcode(postData); | |||||
if (response) { | |||||
console.log(response); | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!); | |||||
} | |||||
setBtnIsLoading(false); | |||||
}, [itemDetail, fetchPoQrcode, downloadFile]); | |||||
const renderSubmitButton = useMemo((): Boolean => { | const renderSubmitButton = useMemo((): Boolean => { | ||||
if (itemDetail) { | if (itemDetail) { | ||||
const status = itemDetail.status; | const status = itemDetail.status; | ||||
console.log(status); | console.log(status); | ||||
switch (type) { | switch (type) { | ||||
case "qc": | case "qc": | ||||
return stockInLineStatusMap[status] >= 1 && stockInLineStatusMap[status] <= 2; | |||||
return ( | |||||
stockInLineStatusMap[status] >= 1 && | |||||
stockInLineStatusMap[status] <= 2 | |||||
); | |||||
case "escalation": | case "escalation": | ||||
return stockInLineStatusMap[status] === 1 || stockInLineStatusMap[status] >= 3 || stockInLineStatusMap[status] <= 5; | |||||
return ( | |||||
stockInLineStatusMap[status] === 1 || | |||||
stockInLineStatusMap[status] >= 3 || | |||||
stockInLineStatusMap[status] <= 5 | |||||
); | |||||
case "stockIn": | case "stockIn": | ||||
return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; | |||||
return ( | |||||
stockInLineStatusMap[status] >= 3 && | |||||
stockInLineStatusMap[status] <= 6 | |||||
); | |||||
case "putaway": | case "putaway": | ||||
return stockInLineStatusMap[status] === 7; | return stockInLineStatusMap[status] === 7; | ||||
default: | default: | ||||
@@ -248,19 +366,30 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
{itemDetail !== undefined && type === "putaway" && ( | {itemDetail !== undefined && type === "putaway" && ( | ||||
<PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | <PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | ||||
)} | )} | ||||
{renderSubmitButton ? ( | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
{renderSubmitButton ? ( | |||||
<Button | <Button | ||||
name="submit" | name="submit" | ||||
variant="contained" | variant="contained" | ||||
startIcon={<Check />} | startIcon={<Check />} | ||||
type="submit" | type="submit" | ||||
// disabled={submitDisabled} | |||||
disabled={btnIsLoading} | |||||
> | > | ||||
{t("submit")} | {t("submit")} | ||||
</Button> | </Button> | ||||
</Stack> | |||||
) : undefined} | |||||
) : undefined} | |||||
{itemDetail !== undefined && type === "putaway" && ( | |||||
<Button | |||||
name="print" | |||||
variant="contained" | |||||
// startIcon={<Check />} | |||||
onClick={printQrcode} | |||||
disabled={btnIsLoading} | |||||
> | |||||
{t("print")} | |||||
</Button> | |||||
)} | |||||
</Stack> | |||||
</Box> | </Box> | ||||
</Modal> | </Modal> | ||||
</FormProvider> | </FormProvider> | ||||
@@ -16,7 +16,7 @@ import { | |||||
Tooltip, | Tooltip, | ||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { useFormContext } from "react-hook-form"; | |||||
import { Controller, useFormContext } from "react-hook-form"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
@@ -40,7 +40,9 @@ import { WarehouseResult } from "@/app/api/warehouse"; | |||||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
import { QRCodeSVG } from "qrcode.react"; | import { QRCodeSVG } from "qrcode.react"; | ||||
import { QrCode } from "../QrCode"; | import { QrCode } from "../QrCode"; | ||||
import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
import ReactQrCodeScanner, { | |||||
ScannerConfig, | |||||
} from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
import { QrCodeInfo } from "@/app/api/qrcode"; | import { QrCodeInfo } from "@/app/api/qrcode"; | ||||
interface Props { | interface Props { | ||||
@@ -84,19 +86,24 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
clearErrors, | clearErrors, | ||||
} = useFormContext<PutawayInput>(); | } = useFormContext<PutawayInput>(); | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
const [recordQty, setRecordQty] = useState(0); | |||||
const [warehouseId, setWarehouseId] = useState(0); | |||||
// const [recordQty, setRecordQty] = useState(0); | |||||
const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); | |||||
const filteredWarehouse = useMemo(() => { | const filteredWarehouse = useMemo(() => { | ||||
// do filtering here if any | // do filtering here if any | ||||
return warehouse; | return warehouse; | ||||
}, []); | }, []); | ||||
const defaultOption = { | |||||
value: 0, // think think sin | |||||
label: t("Select warehouse"), | |||||
group: "default", | |||||
} | |||||
const options = useMemo(() => { | const options = useMemo(() => { | ||||
return [ | return [ | ||||
{ | |||||
value: -1, // think think sin | |||||
label: t("Select warehouse"), | |||||
group: "default", | |||||
}, | |||||
// { | |||||
// value: 0, // think think sin | |||||
// label: t("Select warehouse"), | |||||
// group: "default", | |||||
// }, | |||||
...filteredWarehouse.map((w) => ({ | ...filteredWarehouse.map((w) => ({ | ||||
value: w.id, | value: w.id, | ||||
label: `${w.code} - ${w.name}`, | label: `${w.code} - ${w.name}`, | ||||
@@ -107,8 +114,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
const currentValue = | const currentValue = | ||||
warehouseId > 0 | warehouseId > 0 | ||||
? options.find((o) => o.value === warehouseId) | ? options.find((o) => o.value === warehouseId) | ||||
: options.find((o) => o.value === getValues("warehouseId")) || options[0]; | |||||
: options.find((o) => o.value === getValues("warehouseId")) || defaultOption; | |||||
const onChange = useCallback( | const onChange = useCallback( | ||||
( | ( | ||||
event: React.SyntheticEvent, | event: React.SyntheticEvent, | ||||
@@ -119,19 +126,47 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
group: string; | group: string; | ||||
}; | }; | ||||
console.log(singleNewVal); | console.log(singleNewVal); | ||||
setValue("warehouseId", singleNewVal.value); | |||||
console.log("onChange"); | |||||
// setValue("warehouseId", singleNewVal.value); | |||||
setWarehouseId(singleNewVal.value); | setWarehouseId(singleNewVal.value); | ||||
}, | }, | ||||
[] | [] | ||||
); | ); | ||||
// const accQty = watch("acceptedQty"); | |||||
// const validateForm = useCallback(() => { | |||||
// console.log(accQty); | |||||
// if (accQty > itemDetail.acceptedQty) { | |||||
// setError("acceptedQty", { | |||||
// message: `acceptedQty must not greater than ${itemDetail.acceptedQty}`, | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// if (accQty < 1) { | |||||
// setError("acceptedQty", { | |||||
// message: `minimal value is 1`, | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// if (isNaN(accQty)) { | |||||
// setError("acceptedQty", { | |||||
// message: `value must be a number`, | |||||
// type: "required", | |||||
// }); | |||||
// } | |||||
// }, [accQty]); | |||||
// useEffect(() => { | |||||
// clearErrors(); | |||||
// validateForm(); | |||||
// }, [validateForm]); | |||||
const qrContent = useMemo( | const qrContent = useMemo( | ||||
() => ({ | () => ({ | ||||
stockInLineId: itemDetail.id, | stockInLineId: itemDetail.id, | ||||
itemId: itemDetail.itemId, | itemId: itemDetail.itemId, | ||||
lotNo: itemDetail.lotNo, | lotNo: itemDetail.lotNo, | ||||
// warehouseId: 1 // for testing | |||||
// warehouseId: 2 // for testing | |||||
// expiryDate: itemDetail.expiryDate, | // expiryDate: itemDetail.expiryDate, | ||||
// productionDate: itemDetail.productionDate, | // productionDate: itemDetail.productionDate, | ||||
// supplier: itemDetail.supplier, | // supplier: itemDetail.supplier, | ||||
@@ -140,7 +175,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
[itemDetail] | [itemDetail] | ||||
); | ); | ||||
const [isOpenScanner, setOpenScanner] = useState(false); | const [isOpenScanner, setOpenScanner] = useState(false); | ||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
(...args) => { | (...args) => { | ||||
setOpenScanner(false); | setOpenScanner(false); | ||||
@@ -158,23 +193,33 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
const scannerConfig = useMemo<ScannerConfig>( | const scannerConfig = useMemo<ScannerConfig>( | ||||
() => ({ | () => ({ | ||||
onUpdate: (err, result) => { | onUpdate: (err, result) => { | ||||
console.log(result); | |||||
console.log(Boolean(result)); | |||||
if (result) { | if (result) { | ||||
const data: QrCodeInfo = JSON.parse(result.getText()); | const data: QrCodeInfo = JSON.parse(result.getText()); | ||||
console.log(data); | console.log(data); | ||||
if (data.warehouseId) { | if (data.warehouseId) { | ||||
console.log(data.warehouseId); | |||||
setWarehouseId(data.warehouseId); | setWarehouseId(data.warehouseId); | ||||
onCloseScanner() | |||||
onCloseScanner(); | |||||
} | } | ||||
} else return; | } else return; | ||||
}, | }, | ||||
}), | }), | ||||
[] | |||||
[onCloseScanner] | |||||
); | ); | ||||
useEffect(() => { | useEffect(() => { | ||||
setValue("status", "completed"); | setValue("status", "completed"); | ||||
}, []); | }, []); | ||||
useEffect(() => { | |||||
if (warehouseId > 0) { | |||||
setValue("warehouseId", warehouseId); | |||||
clearErrors("warehouseId") | |||||
} | |||||
}, [warehouseId]); | |||||
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}> | ||||
@@ -260,11 +305,13 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
disableClearable | disableClearable | ||||
disabled | disabled | ||||
fullWidth | fullWidth | ||||
defaultValue={options.find((o) => o.value === 1)} | |||||
defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||||
// onChange={onChange} | // onChange={onChange} | ||||
getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
options={options} | options={options} | ||||
renderInput={(params) => <TextField {...params} label="Default Warehouse"/>} | |||||
renderInput={(params) => ( | |||||
<TextField {...params} label="Default Warehouse" /> | |||||
)} | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
@@ -278,25 +325,63 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
max: itemDetail.acceptedQty, | max: itemDetail.acceptedQty, | ||||
valueAsNumber: true, | valueAsNumber: true, | ||||
})} | })} | ||||
defaultValue={itemDetail.acceptedQty} | |||||
// defaultValue={itemDetail.acceptedQty} | |||||
error={Boolean(errors.acceptedQty)} | error={Boolean(errors.acceptedQty)} | ||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={1}> | <Grid item xs={1}> | ||||
<Button onClick={onOpenScanner}>bind</Button> | |||||
<Button onClick={onOpenScanner}>bind</Button> | |||||
</Grid> | </Grid> | ||||
<Grid item xs={5.5}> | <Grid item xs={5.5}> | ||||
{/* <Controller | |||||
control={control} | |||||
name="warehouseId" | |||||
render={({ field }) => { | |||||
console.log(field); | |||||
return ( | |||||
<Autocomplete | |||||
noOptionsText={t("No Warehouse")} | |||||
disableClearable | |||||
fullWidth | |||||
value={options.find((o) => o.value == field.value)} | |||||
onChange={onChange} | |||||
getOptionLabel={(option) => option.label} | |||||
options={options} | |||||
renderInput={(params) => ( | |||||
<TextField | |||||
{...params} | |||||
label={"Select warehouse"} | |||||
error={Boolean(errors.warehouseId?.message)} | |||||
helperText={warehouseHelperText} | |||||
// helperText={errors.warehouseId?.message} | |||||
/> | |||||
)} | |||||
/> | |||||
); | |||||
}} | |||||
/> */} | |||||
<FormControl fullWidth> | <FormControl fullWidth> | ||||
<Autocomplete | <Autocomplete | ||||
noOptionsText={t("No Warehouse")} | noOptionsText={t("No Warehouse")} | ||||
disableClearable | disableClearable | ||||
fullWidth | fullWidth | ||||
// value={warehouseId > 0 | |||||
// ? options.find((o) => o.value === warehouseId) | |||||
// : undefined} | |||||
value={currentValue} | value={currentValue} | ||||
onChange={onChange} | onChange={onChange} | ||||
getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
options={options} | options={options} | ||||
renderInput={(params) => <TextField {...params} />} | |||||
renderInput={(params) => ( | |||||
<TextField | |||||
{...params} | |||||
// label={"Select warehouse"} | |||||
error={Boolean(errors.warehouseId?.message)} | |||||
helperText={errors.warehouseId?.message} | |||||
// helperText={warehouseHelperText} | |||||
/> | |||||
)} | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
@@ -318,11 +403,11 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
<Button onClick={onOpenScanner}>bind</Button> | <Button onClick={onOpenScanner}>bind</Button> | ||||
</Grid> */} | </Grid> */} | ||||
<Modal open={isOpenScanner} onClose={closeHandler}> | |||||
<Box sx={style}> | |||||
<ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
</Box> | |||||
</Modal> | |||||
<Modal open={isOpenScanner} onClose={closeHandler}> | |||||
<Box sx={style}> | |||||
<ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
</Box> | |||||
</Modal> | |||||
</Grid> | </Grid> | ||||
); | ); | ||||
}; | }; | ||||
@@ -38,7 +38,7 @@ import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | import axiosInstance from "@/app/(main)/axios/axiosInstance"; | ||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine | |||||
itemDetail: StockInLine; | |||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
} | } | ||||
type EntryError = | type EntryError = | ||||
@@ -65,9 +65,37 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
clearErrors, | clearErrors, | ||||
} = useFormContext<PurchaseQCInput>(); | } = useFormContext<PurchaseQCInput>(); | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
console.log(defaultValues); | |||||
const [recordQty, setRecordQty] = useState(0); | |||||
console.log(defaultValues); | |||||
//// validate form | |||||
const accQty = watch("acceptedQty"); | |||||
const validateForm = useCallback(() => { | |||||
console.log(accQty); | |||||
if (accQty > itemDetail.acceptedQty) { | |||||
setError("acceptedQty", { | |||||
message: `acceptedQty must not greater than ${itemDetail.acceptedQty}`, | |||||
type: "required", | |||||
}); | |||||
} | |||||
if (accQty < 1) { | |||||
setError("acceptedQty", { | |||||
message: `minimal value is 1`, | |||||
type: "required", | |||||
}); | |||||
} | |||||
if (isNaN(accQty)) { | |||||
setError("acceptedQty", { | |||||
message: `value must be a number`, | |||||
type: "required", | |||||
}); | |||||
} | |||||
}, [accQty]); | |||||
useEffect(() => { | |||||
clearErrors(); | |||||
validateForm(); | |||||
}, [validateForm]); | |||||
// const [recordQty, setRecordQty] = useState(0); | |||||
const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
{ | { | ||||
@@ -105,7 +133,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
// id: params.id, | // id: params.id, | ||||
// field: "type", | // field: "type", | ||||
// value: "determine1", | // value: "determine1", | ||||
// }); | |||||
// }); | |||||
}} | }} | ||||
/> | /> | ||||
); | ); | ||||
@@ -144,6 +172,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
], | ], | ||||
[qc] | [qc] | ||||
); | ); | ||||
/// validate datagrid | |||||
const validation = useCallback( | const validation = useCallback( | ||||
(newRow: GridRowModel<PoQcRow>): EntryError => { | (newRow: GridRowModel<PoQcRow>): EntryError => { | ||||
const error: EntryError = {}; | const error: EntryError = {}; | ||||
@@ -161,17 +190,17 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
}, | }, | ||||
[] | [] | ||||
); | ); | ||||
useEffect(() => { | useEffect(() => { | ||||
console.log(itemDetail) | |||||
var status = "receiving" | |||||
console.log(itemDetail); | |||||
var status = "receiving"; | |||||
// switch (itemDetail.status) { | // switch (itemDetail.status) { | ||||
// case 'pending': | // case 'pending': | ||||
// status = "receiving" | // status = "receiving" | ||||
// break; | // break; | ||||
// } | // } | ||||
setValue("status", status) | |||||
}, [itemDetail]) | |||||
setValue("status", status); | |||||
}, [itemDetail]); | |||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
@@ -187,21 +216,22 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
spacing={2} | spacing={2} | ||||
sx={{ mt: 0.5 }} | sx={{ mt: 0.5 }} | ||||
> | > | ||||
<Grid item xs={12} lg={6}> | |||||
<Grid item xs={12} lg={12}> | |||||
<TextField | <TextField | ||||
label={t("accepted Qty")} | label={t("accepted Qty")} | ||||
fullWidth | fullWidth | ||||
// value={itemDetail.acceptedQty} | // value={itemDetail.acceptedQty} | ||||
{...register("acceptedQty", { | {...register("acceptedQty", { | ||||
required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
valueAsNumber: true | |||||
valueAsNumber: true, | |||||
max: itemDetail.acceptedQty, | |||||
})} | })} | ||||
// disabled | // disabled | ||||
error={Boolean(errors.acceptedQty)} | error={Boolean(errors.acceptedQty)} | ||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={12} lg={6}> | |||||
{/* <Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("Total record qty")} | label={t("Total record qty")} | ||||
fullWidth | fullWidth | ||||
@@ -213,14 +243,15 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
// error={Boolean(errors.sampleRate)} | // error={Boolean(errors.sampleRate)} | ||||
// helperText={errors.sampleRate?.message} | // helperText={errors.sampleRate?.message} | ||||
/> | /> | ||||
</Grid> | |||||
</Grid> */} | |||||
<Grid item xs={12} lg={6}> | <Grid item xs={12} lg={6}> | ||||
<TextField | <TextField | ||||
label={t("sampleRate")} | label={t("sampleRate")} | ||||
fullWidth | fullWidth | ||||
defaultValue={1} | |||||
{...register("sampleRate", { | {...register("sampleRate", { | ||||
required: "sampleRate required!", | required: "sampleRate required!", | ||||
valueAsNumber: true | |||||
valueAsNumber: true, | |||||
})} | })} | ||||
error={Boolean(errors.sampleRate)} | error={Boolean(errors.sampleRate)} | ||||
helperText={errors.sampleRate?.message} | helperText={errors.sampleRate?.message} | ||||
@@ -230,8 +261,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
<TextField | <TextField | ||||
label={t("sampleWeight")} | label={t("sampleWeight")} | ||||
fullWidth | fullWidth | ||||
defaultValue={1} | |||||
{...register("sampleWeight", { | {...register("sampleWeight", { | ||||
required: "sampleWeight required!", | required: "sampleWeight required!", | ||||
valueAsNumber: true, | |||||
})} | })} | ||||
error={Boolean(errors.sampleWeight)} | error={Boolean(errors.sampleWeight)} | ||||
helperText={errors.sampleWeight?.message} | helperText={errors.sampleWeight?.message} | ||||
@@ -241,8 +274,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
<TextField | <TextField | ||||
label={t("totalWeight")} | label={t("totalWeight")} | ||||
fullWidth | fullWidth | ||||
defaultValue={1} | |||||
{...register("totalWeight", { | {...register("totalWeight", { | ||||
required: "totalWeight required!", | required: "totalWeight required!", | ||||
valueAsNumber: true, | |||||
})} | })} | ||||
error={Boolean(errors.totalWeight)} | error={Boolean(errors.totalWeight)} | ||||
helperText={errors.totalWeight?.message} | helperText={errors.totalWeight?.message} | ||||
@@ -263,7 +298,9 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
_formKey={"qcResult"} | _formKey={"qcResult"} | ||||
columns={columns} | columns={columns} | ||||
validateRow={validation} | validateRow={validation} | ||||
needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} | |||||
needAdd={ | |||||
itemDetail.status === "qc" || itemDetail.status === "pending" | |||||
} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
@@ -1,6 +1,10 @@ | |||||
"use client"; | "use client"; | ||||
import { PurchaseQcResult, PurchaseQCInput, StockInInput } from "@/app/api/po/actions"; | |||||
import { | |||||
PurchaseQcResult, | |||||
PurchaseQCInput, | |||||
StockInInput, | |||||
} from "@/app/api/po/actions"; | |||||
import { | import { | ||||
Box, | Box, | ||||
Card, | Card, | ||||
@@ -11,7 +15,7 @@ import { | |||||
Tooltip, | Tooltip, | ||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { useFormContext } from "react-hook-form"; | |||||
import { Controller, useFormContext } from "react-hook-form"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
import { useCallback, useEffect, useMemo } from "react"; | import { useCallback, useEffect, useMemo } from "react"; | ||||
@@ -31,6 +35,10 @@ import QcSelect from "./QcSelect"; | |||||
import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
import { GridEditInputCell } from "@mui/x-data-grid"; | import { GridEditInputCell } from "@mui/x-data-grid"; | ||||
import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import dayjs from "dayjs"; | |||||
// change PurchaseQcResult to stock in entry props | // change PurchaseQcResult to stock in entry props | ||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
@@ -44,11 +52,14 @@ type EntryError = | |||||
// type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | ||||
const StockInForm: React.FC<Props> = ({ | |||||
const StockInForm: React.FC<Props> = ({ | |||||
// qc, | // qc, | ||||
itemDetail, | itemDetail, | ||||
}) => { | |||||
const { t } = useTranslation(); | |||||
}) => { | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation(); | |||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const { | const { | ||||
register, | register, | ||||
@@ -62,18 +73,33 @@ const StockInForm: React.FC<Props> = ({ | |||||
setError, | setError, | ||||
clearErrors, | clearErrors, | ||||
} = useFormContext<StockInInput>(); | } = useFormContext<StockInInput>(); | ||||
console.log(itemDetail) | |||||
console.log(itemDetail); | |||||
useEffect(() => { | |||||
console.log("triggered"); | |||||
// receiptDate default tdy | |||||
setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||||
setValue("status", "received"); | |||||
}, []); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log("triggered") | |||||
setValue("status", "received") | |||||
}, []) | |||||
console.log(errors); | |||||
}, [errors]); | |||||
const productionDate = watch("productionDate"); | |||||
const expiryDate = watch("expiryDate"); | |||||
useEffect(() => { | |||||
console.log(productionDate) | |||||
console.log(expiryDate) | |||||
if (expiryDate) clearErrors() | |||||
if (productionDate) clearErrors() | |||||
}, [productionDate, expiryDate]) | |||||
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}> | ||||
<Typography variant="h6" display="block" marginBlockEnd={1}> | <Typography variant="h6" display="block" marginBlockEnd={1}> | ||||
{t("Qc Detail")} | |||||
{t("Stock In Detail")} | |||||
</Typography> | </Typography> | ||||
</Grid> | </Grid> | ||||
<Grid | <Grid | ||||
@@ -95,14 +121,38 @@ const StockInForm: React.FC<Props> = ({ | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
<TextField | |||||
label={t("receiptDate")} | |||||
fullWidth | |||||
{...register("receiptDate", { | |||||
required: "receiptDate required!", | |||||
})} | |||||
error={Boolean(errors.receiptDate)} | |||||
helperText={errors.receiptDate?.message} | |||||
<Controller | |||||
control={control} | |||||
name="receiptDate" | |||||
rules={{ required: true }} | |||||
render={({ field }) => { | |||||
return ( | |||||
<LocalizationProvider | |||||
dateAdapter={AdapterDayjs} | |||||
adapterLocale={`${language}-hk`} | |||||
> | |||||
<DatePicker | |||||
{...field} | |||||
sx={{ width: "100%" }} | |||||
label={t("receiptDate")} | |||||
value={dayjs(watch("receiptDate"))} | |||||
onChange={(date) => { | |||||
if (!date) return | |||||
// setValue("receiptDate", date.format(INPUT_DATE_FORMAT)); | |||||
field.onChange(date); | |||||
}} | |||||
inputRef={field.ref} | |||||
slotProps={{ | |||||
textField: { | |||||
// required: true, | |||||
error: Boolean(errors.receiptDate?.message), | |||||
helperText: errors.receiptDate?.message, | |||||
}, | |||||
}} | |||||
/> | |||||
</LocalizationProvider> | |||||
); | |||||
}} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
@@ -128,25 +178,75 @@ const StockInForm: React.FC<Props> = ({ | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
<TextField | |||||
label={t("productionDate")} | |||||
fullWidth | |||||
{...register("productionDate", { | |||||
// required: "productionDate required!", | |||||
})} | |||||
// error={Boolean(errors.productionDate)} | |||||
// helperText={errors.productionDate?.message} | |||||
<Controller | |||||
control={control} | |||||
name="productionDate" | |||||
// rules={{ required: !Boolean(expiryDate) }} | |||||
render={({ field }) => { | |||||
return ( | |||||
<LocalizationProvider | |||||
dateAdapter={AdapterDayjs} | |||||
adapterLocale={`${language}-hk`} | |||||
> | |||||
<DatePicker | |||||
{...field} | |||||
sx={{ width: "100%" }} | |||||
label={t("productionDate")} | |||||
value={productionDate ? dayjs(productionDate) : undefined} | |||||
onChange={(date) => { | |||||
if (!date) return | |||||
setValue("productionDate", date.format(INPUT_DATE_FORMAT)); | |||||
// field.onChange(date); | |||||
}} | |||||
inputRef={field.ref} | |||||
slotProps={{ | |||||
textField: { | |||||
// required: true, | |||||
error: Boolean(errors.productionDate?.message), | |||||
helperText: errors.productionDate?.message, | |||||
}, | |||||
}} | |||||
/> | |||||
</LocalizationProvider> | |||||
); | |||||
}} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
<TextField | |||||
label={t("expiryDate")} | |||||
fullWidth | |||||
{...register("expiryDate", { | |||||
required: "expiryDate required!", | |||||
})} | |||||
error={Boolean(errors.expiryDate)} | |||||
helperText={errors.expiryDate?.message} | |||||
<Controller | |||||
control={control} | |||||
name="expiryDate" | |||||
// rules={{ required: !Boolean(productionDate) }} | |||||
render={({ field }) => { | |||||
return ( | |||||
<LocalizationProvider | |||||
dateAdapter={AdapterDayjs} | |||||
adapterLocale={`${language}-hk`} | |||||
> | |||||
<DatePicker | |||||
{...field} | |||||
sx={{ width: "100%" }} | |||||
label={t("expiryDate")} | |||||
value={expiryDate ? dayjs(expiryDate) : undefined} | |||||
onChange={(date) => { | |||||
console.log(date) | |||||
if (!date) return | |||||
console.log(date.format(INPUT_DATE_FORMAT)) | |||||
setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); | |||||
// field.onChange(date); | |||||
}} | |||||
inputRef={field.ref} | |||||
slotProps={{ | |||||
textField: { | |||||
// required: true, | |||||
error: Boolean(errors.expiryDate?.message), | |||||
helperText: errors.expiryDate?.message, | |||||
}, | |||||
}} | |||||
/> | |||||
</LocalizationProvider> | |||||
); | |||||
}} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
@@ -52,6 +52,14 @@ const PoSearch: React.FC<Props> = ({ po }) => { | |||||
name: "code", | name: "code", | ||||
label: t("Code"), | label: t("Code"), | ||||
}, | }, | ||||
{ | |||||
name: "orderDate", | |||||
label: t("OrderDate"), | |||||
}, | |||||
{ | |||||
name: "status", | |||||
label: t("Status"), | |||||
}, | |||||
// { | // { | ||||
// name: "name", | // name: "name", | ||||
// label: t("Name"), | // label: t("Name"), | ||||
@@ -7,6 +7,10 @@ import { notFound } from "next/navigation"; | |||||
import PoSearchLoading from "./PoSearchLoading"; | import PoSearchLoading from "./PoSearchLoading"; | ||||
import PoSearch from "./PoSearch"; | import PoSearch from "./PoSearch"; | ||||
import { fetchPoList, PoResult } from "@/app/api/po"; | import { fetchPoList, PoResult } from "@/app/api/po"; | ||||
import dayjs from "dayjs"; | |||||
import arraySupport from "dayjs/plugin/arraySupport"; | |||||
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
dayjs.extend(arraySupport); | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof PoSearchLoading; | Loading: typeof PoSearchLoading; | ||||
@@ -26,8 +30,14 @@ const PoSearchWrapper: React.FC<Props> & SubComponents = async ( | |||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
fetchPoList() | fetchPoList() | ||||
]); | ]); | ||||
return <PoSearch po={po} />; | |||||
console.log(po) | |||||
const fixPoDate = po.map((p) => { | |||||
return ({ | |||||
...p, | |||||
orderDate: dayjs(p.orderDate).format(OUTPUT_DATE_FORMAT) | |||||
}) | |||||
}) | |||||
return <PoSearch po={fixPoDate} />; | |||||
}; | }; | ||||
PoSearchWrapper.Loading = PoSearchLoading; | PoSearchWrapper.Loading = PoSearchLoading; | ||||
@@ -29,7 +29,7 @@ const style = { | |||||
export var defaultScannerConfig: ScannerConfig = { | export var defaultScannerConfig: ScannerConfig = { | ||||
onUpdate: (err, result) => { | onUpdate: (err, result) => { | ||||
if (result) { | if (result) { | ||||
const data = JSON.parse(result.getText()) | |||||
const data = JSON.parse(result.getText()); | |||||
console.log(data); | console.log(data); | ||||
} else return; | } else return; | ||||
}, | }, | ||||
@@ -48,19 +48,27 @@ export interface ScannerConfig { | |||||
delay?: number; // Delay between scans in milliseconds. Default is 500ms. | delay?: number; // Delay between scans in milliseconds. Default is 500ms. | ||||
videoConstraints?: MediaTrackConstraints; // Video constraints to pass to the webcam. If not provided, the default constraints will be used. | videoConstraints?: MediaTrackConstraints; // Video constraints to pass to the webcam. If not provided, the default constraints will be used. | ||||
formats?: BarcodeFormat[] | BarcodeStringFormat[]; // Array of barcode formats to decode. If not provided, all formats will be used. A smaller list may improve the speed of the scan. | formats?: BarcodeFormat[] | BarcodeStringFormat[]; // Array of barcode formats to decode. If not provided, all formats will be used. A smaller list may improve the speed of the scan. | ||||
stopStream?: boolean | |||||
stopStream?: boolean; | |||||
} | } | ||||
const ReactQrCodeScanner: React.FC<Props> = ({ | |||||
scannerConfig, | |||||
}) => { | |||||
const [stopStream, setStopStream] = useState(scannerConfig.stopStream || defaultScannerConfig.stopStream || false); | |||||
const ReactQrCodeScanner: React.FC<Props> = ({ scannerConfig }) => { | |||||
const [stopStream, setStopStream] = useState( | |||||
scannerConfig.stopStream || defaultScannerConfig.stopStream || false | |||||
); | |||||
const [torchEnabled, setTorchEnabled] = useState<boolean>(false); | const [torchEnabled, setTorchEnabled] = useState<boolean>(false); | ||||
const _scannerConfig = useMemo(() => ({ | |||||
...defaultScannerConfig, | |||||
...scannerConfig, | |||||
}),[]) | |||||
// const _scannerConfig = useMemo(() => ({ | |||||
// ...defaultScannerConfig, | |||||
// ...scannerConfig, | |||||
// }),[]) | |||||
const [_scannerConfig, setScannerConfig] = useState<ScannerConfig>({ | |||||
...defaultScannerConfig | |||||
}); | |||||
useEffect(() => { | |||||
setScannerConfig({ | |||||
...defaultScannerConfig, | |||||
...scannerConfig, | |||||
}); | |||||
}, []); | |||||
const SwitchOnOffScanner = useCallback(() => { | const SwitchOnOffScanner = useCallback(() => { | ||||
// Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later | // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later | ||||
setStopStream((prev) => !prev); | setStopStream((prev) => !prev); | ||||
@@ -71,21 +79,19 @@ const ReactQrCodeScanner: React.FC<Props> = ({ | |||||
}, []); | }, []); | ||||
return ( | return ( | ||||
<> | |||||
{!stopStream ? ( | |||||
<BarcodeScanner | |||||
stopStream={stopStream} | |||||
torch={torchEnabled} | |||||
{..._scannerConfig} | |||||
/> | |||||
) : undefined} | |||||
<Button onClick={SwitchOnOffTorch}> | |||||
{torchEnabled ? "off" : "on"} | |||||
</Button> | |||||
<Button onClick={SwitchOnOffScanner}> | |||||
{stopStream ? "start" : "stop"} | |||||
</Button> | |||||
</> | |||||
<> | |||||
{!stopStream ? ( | |||||
<BarcodeScanner | |||||
stopStream={stopStream} | |||||
torch={torchEnabled} | |||||
{..._scannerConfig} | |||||
/> | |||||
) : undefined} | |||||
<Button onClick={SwitchOnOffTorch}>{torchEnabled ? "off" : "on"}</Button> | |||||
<Button onClick={SwitchOnOffScanner}> | |||||
{stopStream ? "start" : "stop"} | |||||
</Button> | |||||
</> | |||||
); | ); | ||||
}; | }; | ||||
export default ReactQrCodeScanner; | export default ReactQrCodeScanner; |