@@ -14,8 +14,8 @@ export const metadata: Metadata = { | |||
title: "Purchase Order", | |||
}; | |||
const production: React.FC = async () => { | |||
const { t } = await getServerI18n("claims"); | |||
const PurchaseOrder: React.FC = async () => { | |||
const { t } = await getServerI18n("purchaseOrder"); | |||
// preloadClaims(); | |||
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 | |||
message: string | null; | |||
errorPosition: string | keyof T; | |||
entity: StockInLine | StockInLine[] | |||
entity: T | T[] | |||
// entity: StockInLine | StockInLine[] | |||
} | |||
export interface StockInLineEntry { | |||
@@ -83,6 +84,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
// revalidateTag("po"); | |||
return stockInLine | |||
} | |||
@@ -92,16 +94,34 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
// revalidateTag("po"); | |||
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) => { | |||
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", | |||
body: JSON.stringify({ poId }), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
revalidateTag("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 | |||
price: number | |||
priceUnit: string | |||
productionDate: string | |||
expiryDate: string | |||
shelfLife?: number, | |||
receiptDate?: string | |||
productionDate?: string | |||
expiryDate?: string | |||
status: string | |||
supplier: 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 } = { | |||
"draft": 0, | |||
"pending": 1, | |||
@@ -36,8 +36,10 @@ import { | |||
} from "@mui/x-data-grid"; | |||
import { | |||
checkPolAndCompletePo, | |||
fetchPoInClient, | |||
fetchStockInLineInfo, | |||
PurchaseQcResult, | |||
startPo, | |||
testFetch, | |||
} from "@/app/api/po/actions"; | |||
import { | |||
@@ -88,14 +90,46 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
console.log(cameras); | |||
const { t } = useTranslation(); | |||
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 [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 }) { | |||
const { row } = props; | |||
const [open, setOpen] = useState(false); | |||
const [processedQty, setProcessedQty] = useState(row.processed); | |||
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(() => { | |||
if (processedQty === row.qty) { | |||
setCurrStatus("completed".toUpperCase()); | |||
@@ -106,20 +140,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
} | |||
}, [processedQty]); | |||
const totalWeight = useMemo( | |||
() => calculateWeight(row.qty, row.uom), | |||
[calculateWeight] | |||
); | |||
const weightUnit = useMemo( | |||
() => returnWeightUnit(row.uom), | |||
[returnWeightUnit] | |||
); | |||
return ( | |||
<> | |||
<TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | |||
<TableCell> | |||
<IconButton | |||
disabled={purchaseOrder.status.toLowerCase() === "pending"} | |||
aria-label="expand row" | |||
size="small" | |||
onClick={() => setOpen(!open)} | |||
@@ -152,9 +178,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
<PoInputGrid | |||
qc={qc} | |||
setRows={setRows} | |||
stockInLine={stockInLine} | |||
setStockInLine={setStockInLine} | |||
setProcessedQty={setProcessedQty} | |||
itemDetail={row} | |||
stockInLine={row.stockInLine} | |||
warehouse={warehouse} | |||
/> | |||
</Box> | |||
@@ -199,20 +226,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
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 ( | |||
<> | |||
<Stack | |||
@@ -220,13 +233,26 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
// component="form" | |||
// onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||
> | |||
<Grid container xs={12} justifyContent="space-between"> | |||
<Grid container xs={12} justifyContent="start"> | |||
<Grid item> | |||
<Typography mb={2} variant="h4"> | |||
{po.code} | |||
{/* {purchaseOrder.code} - {currPoStatus} */} | |||
{purchaseOrder.code} - {purchaseOrder.status} | |||
</Typography> | |||
</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"> | |||
<Button onClick={handleComplete}>Complete</Button> | |||
</Grid> */} | |||
@@ -242,9 +268,15 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
</Tabs> | |||
</Grid> | |||
{/* <Grid item xs={4}> */} | |||
{/* scanner */} | |||
{/* scanner */} | |||
{/* </Grid> */} | |||
<Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||
<Grid | |||
item | |||
xs={4} | |||
display="flex" | |||
justifyContent="end" | |||
alignItems="end" | |||
> | |||
<QrModal | |||
open={isOpenScanner} | |||
onClose={onCloseScanner} | |||
@@ -63,6 +63,7 @@ interface ResultWithId { | |||
interface Props { | |||
qc: QcItemWithChecks[]; | |||
setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | |||
setProcessedQty: Dispatch<SetStateAction<number>>; | |||
itemDetail: PurchaseOrderLine; | |||
stockInLine: StockInLine[]; | |||
@@ -100,6 +101,7 @@ class ProcessRowUpdateError extends Error { | |||
function PoInputGrid({ | |||
qc, | |||
setRows, | |||
setStockInLine, | |||
setProcessedQty, | |||
itemDetail, | |||
stockInLine, | |||
@@ -122,6 +124,7 @@ function PoInputGrid({ | |||
const [escalOpen, setEscalOpen] = useState(false); | |||
const [stockInOpen, setStockInOpen] = useState(false); | |||
const [putAwayOpen, setPutAwayOpen] = useState(false); | |||
const [btnIsLoading, setBtnIsLoading] = useState(false); | |||
const [currQty, setCurrQty] = useState(() => { | |||
const total = entries.reduce( | |||
(acc, curr) => acc + (curr.acceptedQty || 0), | |||
@@ -147,17 +150,14 @@ function PoInputGrid({ | |||
); | |||
const handleStart = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
setTimeout(async () => { | |||
// post stock in line | |||
console.log("delayed"); | |||
console.log(params); | |||
const oldId = params.row.id; | |||
console.log(oldId); | |||
console.log(params.row); | |||
const postData = { | |||
itemId: params.row.itemId, | |||
itemNo: params.row.itemNo, | |||
@@ -171,6 +171,13 @@ function PoInputGrid({ | |||
setEntries((prev) => | |||
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 | |||
// openStartModal(); | |||
}, 200); | |||
@@ -183,6 +190,7 @@ function PoInputGrid({ | |||
const handleQC = useCallback( | |||
(id: GridRowId, params: any) => async () => { | |||
setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
@@ -199,12 +207,14 @@ function PoInputGrid({ | |||
// open qc modal | |||
console.log("delayed"); | |||
openQcModal(); | |||
setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[fetchQcDefaultValue] | |||
); | |||
const handleEscalation = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
// setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
@@ -214,12 +224,14 @@ function PoInputGrid({ | |||
// open qc modal | |||
console.log("delayed"); | |||
openEscalationModal(); | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[] | |||
); | |||
const handleStockIn = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
// setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
@@ -231,6 +243,7 @@ function PoInputGrid({ | |||
// return the record with its status as pending | |||
// update layout | |||
console.log("delayed"); | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[] | |||
@@ -238,6 +251,7 @@ function PoInputGrid({ | |||
const handlePutAway = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
// setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
@@ -249,6 +263,7 @@ function PoInputGrid({ | |||
// return the record with its status as pending | |||
// update layout | |||
console.log("delayed"); | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[] | |||
@@ -256,6 +271,7 @@ function PoInputGrid({ | |||
const printQrcode = useCallback( | |||
async (row: any) => { | |||
setBtnIsLoading(true); | |||
console.log(row.id); | |||
const postData = { stockInLineIds: [row.id] }; | |||
// const postData = { stockInLineIds: [42,43,44] }; | |||
@@ -264,6 +280,7 @@ function PoInputGrid({ | |||
console.log(response); | |||
downloadFile(new Uint8Array(response.blobValue), response.filename!!); | |||
} | |||
setBtnIsLoading(false); | |||
}, | |||
[fetchPoQrcode, downloadFile] | |||
); | |||
@@ -319,11 +336,11 @@ function PoInputGrid({ | |||
() => [ | |||
{ | |||
field: "itemNo", | |||
flex: 0.8, | |||
flex: 0.4, | |||
}, | |||
{ | |||
field: "itemName", | |||
flex: 1, | |||
flex: 0.6, | |||
}, | |||
{ | |||
field: "acceptedQty", | |||
@@ -363,7 +380,7 @@ function PoInputGrid({ | |||
field: "actions", | |||
type: "actions", | |||
headerName: "start | qc | escalation | stock in | putaway | delete", | |||
flex: 1, | |||
flex: 1.5, | |||
cellClassName: "actions", | |||
getActions: (params) => { | |||
console.log(params.row.status); | |||
@@ -376,7 +393,7 @@ function PoInputGrid({ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={!(stockInLineStatusMap[status] === 0)} | |||
disabled={btnIsLoading || !(stockInLineStatusMap[status] === 0)} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleStart(params.row.id, params)} | |||
@@ -390,7 +407,7 @@ function PoInputGrid({ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={stockInLineStatusMap[status] < 1} | |||
disabled={btnIsLoading || stockInLineStatusMap[status] < 1} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleQC(params.row.id, params)} | |||
@@ -405,8 +422,9 @@ function PoInputGrid({ | |||
// marginRight: 1, | |||
}} | |||
disabled={ | |||
btnIsLoading || | |||
stockInLineStatusMap[status] <= 0 || | |||
stockInLineStatusMap[status] >= 4 | |||
stockInLineStatusMap[status] >= 5 | |||
} | |||
// set _isNew to false after posting | |||
// or check status | |||
@@ -421,7 +439,11 @@ function PoInputGrid({ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} | |||
disabled={ | |||
btnIsLoading || | |||
stockInLineStatusMap[status] <= 2 || | |||
stockInLineStatusMap[status] >= 7 | |||
} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleStockIn(params.row.id, params)} | |||
@@ -435,7 +457,7 @@ function PoInputGrid({ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={stockInLineStatusMap[status] < 7} | |||
disabled={btnIsLoading || stockInLineStatusMap[status] < 7} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handlePutAway(params.row.id, params)} | |||
@@ -449,7 +471,7 @@ function PoInputGrid({ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={stockInLineStatusMap[status] !== 8} | |||
disabled={btnIsLoading || stockInLineStatusMap[status] !== 8} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleQrCode(params.row.id, params)} | |||
@@ -462,7 +484,7 @@ function PoInputGrid({ | |||
sx={{ | |||
color: "error.main", | |||
}} | |||
disabled={stockInLineStatusMap[status] !== 0} | |||
disabled={btnIsLoading || stockInLineStatusMap[status] !== 0} | |||
// disabled={Boolean(params.row.status)} | |||
onClick={handleDelete(params.row.id)} | |||
color="inherit" | |||
@@ -472,7 +494,7 @@ function PoInputGrid({ | |||
}, | |||
}, | |||
], | |||
[] | |||
[btnIsLoading] | |||
); | |||
const addRow = useCallback(() => { | |||
@@ -533,6 +555,8 @@ function PoInputGrid({ | |||
const newEntries = entries.map((e) => | |||
getRowId(e) === getRowId(originalRow) ? rowToSave : e | |||
); | |||
setStockInLine(newEntries as StockInLine[]); | |||
console.log("triggered"); | |||
setEntries(newEntries); | |||
//update remaining qty | |||
const total = newEntries.reduce( | |||
@@ -569,9 +593,7 @@ function PoInputGrid({ | |||
</Button> | |||
</Box> | |||
); | |||
useEffect(() => { | |||
console.log(modalInfo); | |||
}, [modalInfo]); | |||
return ( | |||
<> | |||
<StyledDataGrid | |||
@@ -623,7 +645,9 @@ function PoInputGrid({ | |||
<> | |||
<PoQcStockInModal | |||
type={"qc"} | |||
// setRows={setRows} | |||
setEntries={setEntries} | |||
setStockInLine={setStockInLine} | |||
setItemDetail={setModalInfo} | |||
qc={qc} | |||
open={qcOpen} | |||
@@ -636,7 +660,9 @@ function PoInputGrid({ | |||
<> | |||
<PoQcStockInModal | |||
type={"escalation"} | |||
// setRows={setRows} | |||
setEntries={setEntries} | |||
setStockInLine={setStockInLine} | |||
setItemDetail={setModalInfo} | |||
// qc={qc} | |||
open={escalOpen} | |||
@@ -649,7 +675,9 @@ function PoInputGrid({ | |||
<> | |||
<PoQcStockInModal | |||
type={"stockIn"} | |||
// setRows={setRows} | |||
setEntries={setEntries} | |||
setStockInLine={setStockInLine} | |||
// qc={qc} | |||
setItemDetail={setModalInfo} | |||
open={stockInOpen} | |||
@@ -662,7 +690,9 @@ function PoInputGrid({ | |||
<> | |||
<PoQcStockInModal | |||
type={"putaway"} | |||
// setRows={setRows} | |||
setEntries={setEntries} | |||
setStockInLine={setStockInLine} | |||
setItemDetail={setModalInfo} | |||
open={putAwayOpen} | |||
warehouse={warehouse} | |||
@@ -22,23 +22,35 @@ import { useTranslation } from "react-i18next"; | |||
import QcForm from "./QcForm"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
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 { StockInLineRow } from "./PoInputGrid"; | |||
import EscalationForm from "./EscalationForm"; | |||
import StockInForm from "./StockInForm"; | |||
import PutawayForm from "./PutawayForm"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { | |||
INPUT_DATE_FORMAT, | |||
stockInLineStatusMap, | |||
} from "@/app/utils/formatUtil"; | |||
import dayjs from "dayjs"; | |||
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"> { | |||
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | |||
setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | |||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
setItemDetail: Dispatch<SetStateAction<(StockInLine & { | |||
warehouseId?: number; | |||
}) | undefined>> | |||
setItemDetail: Dispatch< | |||
SetStateAction< | |||
| (StockInLine & { | |||
warehouseId?: number; | |||
}) | |||
| undefined | |||
> | |||
>; | |||
qc?: QcItemWithChecks[]; | |||
warehouse?: any[]; | |||
type: "qc" | "stockIn" | "escalation" | "putaway"; | |||
@@ -75,7 +87,9 @@ const style = { | |||
const PoQcStockInModal: React.FC<Props> = ({ | |||
type, | |||
// setRows, | |||
setEntries, | |||
setStockInLine, | |||
open, | |||
onClose, | |||
itemDetail, | |||
@@ -86,12 +100,16 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
const [serverError, setServerError] = useState(""); | |||
const { t } = useTranslation(); | |||
const params = useSearchParams(); | |||
const [btnIsLoading, setBtnIsLoading] = useState(false); | |||
console.log(params.get("id")); | |||
console.log(itemDetail); | |||
console.log(itemDetail.qcResult); | |||
const formProps = useForm<ModalFormInput>({ | |||
defaultValues: { | |||
...itemDetail, | |||
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||
// warehouseId: itemDetail.defaultWarehouseId || 0 | |||
}, | |||
}); | |||
// console.log(formProps); | |||
@@ -111,40 +129,95 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
} | |||
}, [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 & {}>>( | |||
async (data, event) => { | |||
formProps.clearErrors(); | |||
let hasErrors = false; | |||
setBtnIsLoading(true); | |||
console.log(errors); | |||
console.log(data); | |||
console.log(itemDetail); | |||
console.log(data.receiptDate) | |||
console.log(fix0IndexedDate(data.receiptDate)) | |||
// console.log(fix0IndexedDate(data.receiptDate)); | |||
try { | |||
// 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 ////////////////////// | |||
var productionDate = null; | |||
var expiryDate = null; | |||
var receiptDate = 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 (data.qcResult) { | |||
@@ -163,17 +236,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
receiptDate: receiptDate, | |||
} as StockInLineEntry & ModalFormInput; | |||
////////////////////////////////////////////////////////////////////// | |||
console.log(args); | |||
// return | |||
if (hasErrors) { | |||
console.log(args); | |||
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); | |||
if (Boolean(res.id)) { | |||
// update entries | |||
const newEntries = res.entity as StockInLine[]; | |||
console.log(newEntries) | |||
console.log(newEntries); | |||
if (setEntries) { | |||
setEntries((prev) => { | |||
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); | |||
if (index !== -1) { | |||
// 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; | |||
} else { | |||
// Add new item | |||
@@ -192,31 +285,56 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
}); | |||
} | |||
// add loading | |||
setItemDetail(undefined) | |||
setBtnIsLoading(false); | |||
setItemDetail(undefined); | |||
closeHandler({}, "backdropClick"); | |||
} | |||
console.log(res); | |||
// if (res) | |||
} catch (e) { | |||
// server error | |||
setBtnIsLoading(false); | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
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 => { | |||
if (itemDetail) { | |||
const status = itemDetail.status; | |||
console.log(status); | |||
switch (type) { | |||
case "qc": | |||
return stockInLineStatusMap[status] >= 1 && stockInLineStatusMap[status] <= 2; | |||
return ( | |||
stockInLineStatusMap[status] >= 1 && | |||
stockInLineStatusMap[status] <= 2 | |||
); | |||
case "escalation": | |||
return stockInLineStatusMap[status] === 1 || stockInLineStatusMap[status] >= 3 || stockInLineStatusMap[status] <= 5; | |||
return ( | |||
stockInLineStatusMap[status] === 1 || | |||
stockInLineStatusMap[status] >= 3 || | |||
stockInLineStatusMap[status] <= 5 | |||
); | |||
case "stockIn": | |||
return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; | |||
return ( | |||
stockInLineStatusMap[status] >= 3 && | |||
stockInLineStatusMap[status] <= 6 | |||
); | |||
case "putaway": | |||
return stockInLineStatusMap[status] === 7; | |||
default: | |||
@@ -248,19 +366,30 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
{itemDetail !== undefined && type === "putaway" && ( | |||
<PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | |||
)} | |||
{renderSubmitButton ? ( | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
{renderSubmitButton ? ( | |||
<Button | |||
name="submit" | |||
variant="contained" | |||
startIcon={<Check />} | |||
type="submit" | |||
// disabled={submitDisabled} | |||
disabled={btnIsLoading} | |||
> | |||
{t("submit")} | |||
</Button> | |||
</Stack> | |||
) : undefined} | |||
) : undefined} | |||
{itemDetail !== undefined && type === "putaway" && ( | |||
<Button | |||
name="print" | |||
variant="contained" | |||
// startIcon={<Check />} | |||
onClick={printQrcode} | |||
disabled={btnIsLoading} | |||
> | |||
{t("print")} | |||
</Button> | |||
)} | |||
</Stack> | |||
</Box> | |||
</Modal> | |||
</FormProvider> | |||
@@ -16,7 +16,7 @@ import { | |||
Tooltip, | |||
Typography, | |||
} from "@mui/material"; | |||
import { useFormContext } from "react-hook-form"; | |||
import { Controller, useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
@@ -40,7 +40,9 @@ import { WarehouseResult } from "@/app/api/warehouse"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { QRCodeSVG } from "qrcode.react"; | |||
import { QrCode } from "../QrCode"; | |||
import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
import ReactQrCodeScanner, { | |||
ScannerConfig, | |||
} from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
import { QrCodeInfo } from "@/app/api/qrcode"; | |||
interface Props { | |||
@@ -84,19 +86,24 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
clearErrors, | |||
} = useFormContext<PutawayInput>(); | |||
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(() => { | |||
// do filtering here if any | |||
return warehouse; | |||
}, []); | |||
const defaultOption = { | |||
value: 0, // think think sin | |||
label: t("Select warehouse"), | |||
group: "default", | |||
} | |||
const options = useMemo(() => { | |||
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) => ({ | |||
value: w.id, | |||
label: `${w.code} - ${w.name}`, | |||
@@ -107,8 +114,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
const currentValue = | |||
warehouseId > 0 | |||
? 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( | |||
( | |||
event: React.SyntheticEvent, | |||
@@ -119,19 +126,47 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
group: string; | |||
}; | |||
console.log(singleNewVal); | |||
setValue("warehouseId", singleNewVal.value); | |||
console.log("onChange"); | |||
// setValue("warehouseId", 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( | |||
() => ({ | |||
stockInLineId: itemDetail.id, | |||
itemId: itemDetail.itemId, | |||
lotNo: itemDetail.lotNo, | |||
// warehouseId: 1 // for testing | |||
// warehouseId: 2 // for testing | |||
// expiryDate: itemDetail.expiryDate, | |||
// productionDate: itemDetail.productionDate, | |||
// supplier: itemDetail.supplier, | |||
@@ -140,7 +175,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
[itemDetail] | |||
); | |||
const [isOpenScanner, setOpenScanner] = useState(false); | |||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
(...args) => { | |||
setOpenScanner(false); | |||
@@ -158,23 +193,33 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
const scannerConfig = useMemo<ScannerConfig>( | |||
() => ({ | |||
onUpdate: (err, result) => { | |||
console.log(result); | |||
console.log(Boolean(result)); | |||
if (result) { | |||
const data: QrCodeInfo = JSON.parse(result.getText()); | |||
console.log(data); | |||
if (data.warehouseId) { | |||
console.log(data.warehouseId); | |||
setWarehouseId(data.warehouseId); | |||
onCloseScanner() | |||
onCloseScanner(); | |||
} | |||
} else return; | |||
}, | |||
}), | |||
[] | |||
[onCloseScanner] | |||
); | |||
useEffect(() => { | |||
setValue("status", "completed"); | |||
}, []); | |||
useEffect(() => { | |||
if (warehouseId > 0) { | |||
setValue("warehouseId", warehouseId); | |||
clearErrors("warehouseId") | |||
} | |||
}, [warehouseId]); | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
@@ -260,11 +305,13 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
disableClearable | |||
disabled | |||
fullWidth | |||
defaultValue={options.find((o) => o.value === 1)} | |||
defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||
// onChange={onChange} | |||
getOptionLabel={(option) => option.label} | |||
options={options} | |||
renderInput={(params) => <TextField {...params} label="Default Warehouse"/>} | |||
renderInput={(params) => ( | |||
<TextField {...params} label="Default Warehouse" /> | |||
)} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
@@ -278,25 +325,63 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
max: itemDetail.acceptedQty, | |||
valueAsNumber: true, | |||
})} | |||
defaultValue={itemDetail.acceptedQty} | |||
// defaultValue={itemDetail.acceptedQty} | |||
error={Boolean(errors.acceptedQty)} | |||
helperText={errors.acceptedQty?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={1}> | |||
<Button onClick={onOpenScanner}>bind</Button> | |||
<Button onClick={onOpenScanner}>bind</Button> | |||
</Grid> | |||
<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> | |||
<Autocomplete | |||
noOptionsText={t("No Warehouse")} | |||
disableClearable | |||
fullWidth | |||
// value={warehouseId > 0 | |||
// ? options.find((o) => o.value === warehouseId) | |||
// : undefined} | |||
value={currentValue} | |||
onChange={onChange} | |||
getOptionLabel={(option) => option.label} | |||
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> | |||
</Grid> | |||
@@ -318,11 +403,11 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
<Button onClick={onOpenScanner}>bind</Button> | |||
</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> | |||
); | |||
}; | |||
@@ -38,7 +38,7 @@ import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
interface Props { | |||
itemDetail: StockInLine | |||
itemDetail: StockInLine; | |||
qc: QcItemWithChecks[]; | |||
} | |||
type EntryError = | |||
@@ -65,9 +65,37 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
clearErrors, | |||
} = useFormContext<PurchaseQCInput>(); | |||
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[]>( | |||
() => [ | |||
{ | |||
@@ -105,7 +133,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
// id: params.id, | |||
// field: "type", | |||
// value: "determine1", | |||
// }); | |||
// }); | |||
}} | |||
/> | |||
); | |||
@@ -144,6 +172,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
], | |||
[qc] | |||
); | |||
/// validate datagrid | |||
const validation = useCallback( | |||
(newRow: GridRowModel<PoQcRow>): EntryError => { | |||
const error: EntryError = {}; | |||
@@ -161,17 +190,17 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
}, | |||
[] | |||
); | |||
useEffect(() => { | |||
console.log(itemDetail) | |||
var status = "receiving" | |||
console.log(itemDetail); | |||
var status = "receiving"; | |||
// switch (itemDetail.status) { | |||
// case 'pending': | |||
// status = "receiving" | |||
// break; | |||
// } | |||
setValue("status", status) | |||
}, [itemDetail]) | |||
setValue("status", status); | |||
}, [itemDetail]); | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
@@ -187,21 +216,22 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
spacing={2} | |||
sx={{ mt: 0.5 }} | |||
> | |||
<Grid item xs={12} lg={6}> | |||
<Grid item xs={12} lg={12}> | |||
<TextField | |||
label={t("accepted Qty")} | |||
fullWidth | |||
// value={itemDetail.acceptedQty} | |||
{...register("acceptedQty", { | |||
required: "acceptedQty required!", | |||
valueAsNumber: true | |||
valueAsNumber: true, | |||
max: itemDetail.acceptedQty, | |||
})} | |||
// disabled | |||
error={Boolean(errors.acceptedQty)} | |||
helperText={errors.acceptedQty?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={12} lg={6}> | |||
{/* <Grid item xs={12} lg={6}> | |||
<TextField | |||
label={t("Total record qty")} | |||
fullWidth | |||
@@ -213,14 +243,15 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
// error={Boolean(errors.sampleRate)} | |||
// helperText={errors.sampleRate?.message} | |||
/> | |||
</Grid> | |||
</Grid> */} | |||
<Grid item xs={12} lg={6}> | |||
<TextField | |||
label={t("sampleRate")} | |||
fullWidth | |||
defaultValue={1} | |||
{...register("sampleRate", { | |||
required: "sampleRate required!", | |||
valueAsNumber: true | |||
valueAsNumber: true, | |||
})} | |||
error={Boolean(errors.sampleRate)} | |||
helperText={errors.sampleRate?.message} | |||
@@ -230,8 +261,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
<TextField | |||
label={t("sampleWeight")} | |||
fullWidth | |||
defaultValue={1} | |||
{...register("sampleWeight", { | |||
required: "sampleWeight required!", | |||
valueAsNumber: true, | |||
})} | |||
error={Boolean(errors.sampleWeight)} | |||
helperText={errors.sampleWeight?.message} | |||
@@ -241,8 +274,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
<TextField | |||
label={t("totalWeight")} | |||
fullWidth | |||
defaultValue={1} | |||
{...register("totalWeight", { | |||
required: "totalWeight required!", | |||
valueAsNumber: true, | |||
})} | |||
error={Boolean(errors.totalWeight)} | |||
helperText={errors.totalWeight?.message} | |||
@@ -263,7 +298,9 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
_formKey={"qcResult"} | |||
columns={columns} | |||
validateRow={validation} | |||
needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} | |||
needAdd={ | |||
itemDetail.status === "qc" || itemDetail.status === "pending" | |||
} | |||
/> | |||
</Grid> | |||
</Grid> | |||
@@ -1,6 +1,10 @@ | |||
"use client"; | |||
import { PurchaseQcResult, PurchaseQCInput, StockInInput } from "@/app/api/po/actions"; | |||
import { | |||
PurchaseQcResult, | |||
PurchaseQCInput, | |||
StockInInput, | |||
} from "@/app/api/po/actions"; | |||
import { | |||
Box, | |||
Card, | |||
@@ -11,7 +15,7 @@ import { | |||
Tooltip, | |||
Typography, | |||
} from "@mui/material"; | |||
import { useFormContext } from "react-hook-form"; | |||
import { Controller, useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useEffect, useMemo } from "react"; | |||
@@ -31,6 +35,10 @@ import QcSelect from "./QcSelect"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||
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 | |||
interface Props { | |||
itemDetail: StockInLine; | |||
@@ -44,11 +52,14 @@ type EntryError = | |||
// type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
const StockInForm: React.FC<Props> = ({ | |||
const StockInForm: React.FC<Props> = ({ | |||
// qc, | |||
itemDetail, | |||
}) => { | |||
const { t } = useTranslation(); | |||
}) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
} = useTranslation(); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
register, | |||
@@ -62,18 +73,33 @@ const StockInForm: React.FC<Props> = ({ | |||
setError, | |||
clearErrors, | |||
} = 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(() => { | |||
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 ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||
{t("Qc Detail")} | |||
{t("Stock In Detail")} | |||
</Typography> | |||
</Grid> | |||
<Grid | |||
@@ -95,14 +121,38 @@ const StockInForm: React.FC<Props> = ({ | |||
/> | |||
</Grid> | |||
<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 item xs={4}> | |||
@@ -128,25 +178,75 @@ const StockInForm: React.FC<Props> = ({ | |||
/> | |||
</Grid> | |||
<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 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> | |||
@@ -52,6 +52,14 @@ const PoSearch: React.FC<Props> = ({ po }) => { | |||
name: "code", | |||
label: t("Code"), | |||
}, | |||
{ | |||
name: "orderDate", | |||
label: t("OrderDate"), | |||
}, | |||
{ | |||
name: "status", | |||
label: t("Status"), | |||
}, | |||
// { | |||
// name: "name", | |||
// label: t("Name"), | |||
@@ -7,6 +7,10 @@ import { notFound } from "next/navigation"; | |||
import PoSearchLoading from "./PoSearchLoading"; | |||
import PoSearch from "./PoSearch"; | |||
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 { | |||
Loading: typeof PoSearchLoading; | |||
@@ -26,8 +30,14 @@ const PoSearchWrapper: React.FC<Props> & SubComponents = async ( | |||
] = await Promise.all([ | |||
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; | |||
@@ -29,7 +29,7 @@ const style = { | |||
export var defaultScannerConfig: ScannerConfig = { | |||
onUpdate: (err, result) => { | |||
if (result) { | |||
const data = JSON.parse(result.getText()) | |||
const data = JSON.parse(result.getText()); | |||
console.log(data); | |||
} else return; | |||
}, | |||
@@ -48,19 +48,27 @@ export interface ScannerConfig { | |||
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. | |||
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 _scannerConfig = useMemo(() => ({ | |||
...defaultScannerConfig, | |||
...scannerConfig, | |||
}),[]) | |||
// const _scannerConfig = useMemo(() => ({ | |||
// ...defaultScannerConfig, | |||
// ...scannerConfig, | |||
// }),[]) | |||
const [_scannerConfig, setScannerConfig] = useState<ScannerConfig>({ | |||
...defaultScannerConfig | |||
}); | |||
useEffect(() => { | |||
setScannerConfig({ | |||
...defaultScannerConfig, | |||
...scannerConfig, | |||
}); | |||
}, []); | |||
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 | |||
setStopStream((prev) => !prev); | |||
@@ -71,21 +79,19 @@ const ReactQrCodeScanner: React.FC<Props> = ({ | |||
}, []); | |||
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; |