@@ -26,3 +26,10 @@ export const convertObjToURLSearchParams = <T extends object>( | |||
return params.toString(); | |||
}; | |||
export const getCustomWidth = (): number => { | |||
const LIMIT_WIDTH = 1000 | |||
const CUSTOM_WIDTH = 1100 | |||
if (window.innerWidth < LIMIT_WIDTH) return CUSTOM_WIDTH | |||
return window.innerWidth | |||
} |
@@ -37,7 +37,9 @@ import { | |||
GridApiCommunity, | |||
GridSlotsComponentsProps, | |||
} from "@mui/x-data-grid/internals"; | |||
// T == CreatexxxInputs map of the form's fields | |||
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||
// E == error | |||
interface ResultWithId { | |||
id: string | number; | |||
} | |||
@@ -97,7 +99,7 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||
Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||
} | |||
} | |||
// T == CreatexxxInputs | |||
// T == CreatexxxInputs map of the form's fields | |||
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||
// E == error | |||
function InputDataGrid<T, V, E>({ | |||
@@ -126,7 +128,11 @@ function InputDataGrid<T, V, E>({ | |||
const list: TableRow<V, E>[] = getValues(formKey); | |||
return list && list.length > 0 ? list : []; | |||
}); | |||
const originalRows = list && list.length > 0 ? list : []; | |||
// const originalRows = list && list.length > 0 ? list : []; | |||
const originalRows = useMemo(() => ( | |||
list && list.length > 0 ? list : [] | |||
), [list]) | |||
// const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | |||
const [rowSelectionModel, setRowSelectionModel] = | |||
useState<GridRowSelectionModel>(() => { | |||
@@ -154,7 +160,7 @@ function InputDataGrid<T, V, E>({ | |||
console.log(errors); | |||
apiRef.current.updateRows([{ ...row, _error: errors }]); | |||
}, | |||
[apiRef, rowModesModel], | |||
[apiRef], | |||
); | |||
const processRowUpdate = useCallback( | |||
@@ -202,7 +208,7 @@ function InputDataGrid<T, V, E>({ | |||
const reset = useCallback(() => { | |||
setRowModesModel({}); | |||
setRows(originalRows); | |||
}, []); | |||
}, [originalRows]); | |||
const handleCancel = useCallback( | |||
(id: GridRowId) => () => { | |||
@@ -219,14 +225,14 @@ function InputDataGrid<T, V, E>({ | |||
); | |||
} | |||
}, | |||
[setRowModesModel, rows], | |||
[rows, getRowId], | |||
); | |||
const handleDelete = useCallback( | |||
(id: GridRowId) => () => { | |||
setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | |||
}, | |||
[], | |||
[getRowId], | |||
); | |||
const _columns = useMemo<GridColDef[]>( | |||
@@ -281,7 +287,7 @@ function InputDataGrid<T, V, E>({ | |||
// console.log(formKey) | |||
// console.log(rows) | |||
setValue(formKey, rows); | |||
}, [formKey, rows]); | |||
}, [formKey, rows, setValue]); | |||
const footer = ( | |||
<Box display="flex" gap={2} alignItems="center"> | |||
@@ -64,7 +64,8 @@ const style = { | |||
pt: 5, | |||
px: 5, | |||
pb: 10, | |||
width: 1500, | |||
// width: 1500, | |||
width: { xs: "100%", sm: "100%", md: "100%" }, | |||
}; | |||
interface DisableButton { | |||
releaseBtn: boolean; | |||
@@ -107,6 +107,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
{ | |||
field: "itemId", | |||
headerName: t("Item"), | |||
// width: 100, | |||
flex: 1, | |||
editable: true, | |||
valueFormatter(params) { | |||
@@ -162,6 +163,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
{ | |||
field: "qty", | |||
headerName: t("qty"), | |||
// width: 100, | |||
flex: 1, | |||
type: "number", | |||
editable: true, | |||
@@ -181,6 +183,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
{ | |||
field: "uom", | |||
headerName: t("uom"), | |||
// width: 100, | |||
flex: 1, | |||
editable: true, | |||
// renderEditCell(params: GridRenderEditCellParams<PolRow>) { | |||
@@ -257,42 +260,42 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
</FormControl> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<Controller | |||
control={control} | |||
name="targetDate" | |||
// rules={{ required: !Boolean(productionDate) }} | |||
render={({ field }) => { | |||
return ( | |||
<LocalizationProvider | |||
dateAdapter={AdapterDayjs} | |||
adapterLocale={`${language}-hk`} | |||
> | |||
<DatePicker | |||
{...field} | |||
sx={{ width: "100%" }} | |||
label={t("targetDate")} | |||
value={targetDate ? dayjs(targetDate) : undefined} | |||
onChange={(date) => { | |||
console.log(date); | |||
if (!date) return; | |||
console.log(date.format(INPUT_DATE_FORMAT)); | |||
setValue("targetDate", date.format(INPUT_DATE_FORMAT)); | |||
// field.onChange(date); | |||
}} | |||
inputRef={field.ref} | |||
slotProps={{ | |||
textField: { | |||
// required: true, | |||
error: Boolean(errors.targetDate?.message), | |||
helperText: errors.targetDate?.message, | |||
}, | |||
}} | |||
/> | |||
</LocalizationProvider> | |||
); | |||
}} | |||
/> | |||
</Grid> | |||
<Controller | |||
control={control} | |||
name="targetDate" | |||
// rules={{ required: !Boolean(productionDate) }} | |||
render={({ field }) => { | |||
return ( | |||
<LocalizationProvider | |||
dateAdapter={AdapterDayjs} | |||
adapterLocale={`${language}-hk`} | |||
> | |||
<DatePicker | |||
{...field} | |||
sx={{ width: "100%" }} | |||
label={t("targetDate")} | |||
value={targetDate ? dayjs(targetDate) : undefined} | |||
onChange={(date) => { | |||
console.log(date); | |||
if (!date) return; | |||
console.log(date.format(INPUT_DATE_FORMAT)); | |||
setValue("targetDate", date.format(INPUT_DATE_FORMAT)); | |||
// field.onChange(date); | |||
}} | |||
inputRef={field.ref} | |||
slotProps={{ | |||
textField: { | |||
// required: true, | |||
error: Boolean(errors.targetDate?.message), | |||
helperText: errors.targetDate?.message, | |||
}, | |||
}} | |||
/> | |||
</LocalizationProvider> | |||
); | |||
}} | |||
/> | |||
</Grid> | |||
</Grid> | |||
<Grid | |||
container | |||
@@ -21,7 +21,7 @@ const style = { | |||
px: 5, | |||
pb: 10, | |||
display: "block", | |||
width: { xs: "60%", sm: "60%", md: "60%" }, | |||
width: { xs: "100%", sm: "100%", md: "100%" }, | |||
}; | |||
interface Props extends Omit<ModalProps, "children"> { | |||
@@ -62,7 +62,7 @@ const CreatePickOrderModal: React.FC<Props> = ({ | |||
return ( | |||
<> | |||
<FormProvider {...formProps}> | |||
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}> | |||
<Modal open={open} onClose={closeHandler}> | |||
<Box | |||
sx={style} | |||
component="form" | |||
@@ -41,41 +41,27 @@ import { | |||
fetchStockInLineInfo, | |||
PurchaseQcResult, | |||
startPo, | |||
testFetch, | |||
} from "@/app/api/po/actions"; | |||
import { | |||
use, | |||
useCallback, | |||
useContext, | |||
useEffect, | |||
useMemo, | |||
useState, | |||
} from "react"; | |||
import { FormProvider, useForm } from "react-hook-form"; | |||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||
import InputDataGrid, { | |||
TableRow as InputTableRow, | |||
} from "../InputDataGrid/InputDataGrid"; | |||
import PoInputGrid from "./PoInputGrid"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { useSearchParams } from "next/navigation"; | |||
import { WarehouseResult } from "@/app/api/warehouse"; | |||
import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | |||
import QrCodeScanner from "../QrCodeScanner"; | |||
import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||
import { CameraContext } from "../Cameras/CameraProvider"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { QrCodeInfo } from "@/app/api/qrcode"; | |||
import { fetchQcResult } from "@/app/api/qc/actions"; | |||
import PoQcStockInModal from "./PoQcStockInModal"; | |||
import ReactQrCodeScannerModal, { | |||
ScannerConfig, | |||
} from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
import QrModal from "./QrModal"; | |||
import { PlayArrow } from "@mui/icons-material"; | |||
import DoneIcon from "@mui/icons-material/Done"; | |||
import { QrCode } from "../QrCode"; | |||
import { getCustomWidth } from "@/app/utils/commonUtil"; | |||
type Props = { | |||
po: PoResult; | |||
@@ -272,8 +258,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
}; | |||
// break; | |||
} | |||
}, [purchaseOrder, handleStartPo, handleCompletePo]); | |||
}, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); | |||
console.log(window.innerWidth) | |||
return ( | |||
<> | |||
<Stack | |||
@@ -301,20 +288,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
{buttonData.buttonText} | |||
</Button> | |||
</Grid> | |||
{/* {purchaseOrder.status.toLowerCase() === "pending" && ( | |||
<Grid item> | |||
<Button onClick={handleStartPo}>Start</Button> | |||
</Grid> | |||
)} | |||
{purchaseOrder.status.toLowerCase() === "receiving" && ( | |||
<Grid item> | |||
<Button onClick={handleCompletePo}>Complete</Button> | |||
</Grid> | |||
)} */} | |||
</Grid> | |||
{/* <Grid container xs={12} justifyContent="space-between"> | |||
<Button onClick={handleCompletePo}>Complete</Button> | |||
</Grid> */} | |||
<Grid container xs={12} justifyContent="space-between"> | |||
<Grid item xs={8}> | |||
<Tabs | |||
@@ -346,7 +320,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
</Grid> | |||
{/* tab 1 */} | |||
<Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | |||
<TableContainer component={Paper}> | |||
<TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}> | |||
{/* <TableContainer component={Paper} sx={{width: getCustomWidth(), overflow: 'auto' }}> */} | |||
<Table aria-label="collapsible table" stickyHeader> | |||
<TableHead> | |||
<TableRow> | |||
@@ -147,7 +147,7 @@ function PoInputGrid({ | |||
0, | |||
); | |||
setProcessedQty(processedQty); | |||
}, [entries]); | |||
}, [entries, setProcessedQty]); | |||
const handleDelete = useCallback( | |||
(id: GridRowId) => () => { | |||
@@ -155,6 +155,42 @@ function PoInputGrid({ | |||
}, | |||
[getRowId], | |||
); | |||
const closeQcModal = useCallback(() => { | |||
setQcOpen(false); | |||
}, []); | |||
const openQcModal = useCallback(() => { | |||
setQcOpen(true); | |||
}, []); | |||
const closeStockInModal = useCallback(() => { | |||
setStockInOpen(false); | |||
}, []); | |||
const openStockInModal = useCallback(() => { | |||
setStockInOpen(true); | |||
}, []); | |||
const closePutAwayModal = useCallback(() => { | |||
setPutAwayOpen(false); | |||
}, []); | |||
const openPutAwayModal = useCallback(() => { | |||
setPutAwayOpen(true); | |||
}, []); | |||
const closeEscalationModal = useCallback(() => { | |||
setEscalOpen(false); | |||
}, []); | |||
const openEscalationModal = useCallback(() => { | |||
setEscalOpen(true); | |||
}, []); | |||
const closeRejectModal = useCallback(() => { | |||
setRejectOpen(false); | |||
}, []); | |||
const openRejectModal = useCallback(() => { | |||
setRejectOpen(true); | |||
}, []); | |||
const handleStart = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
setBtnIsLoading(true); | |||
@@ -189,7 +225,7 @@ function PoInputGrid({ | |||
// openStartModal(); | |||
}, 200); | |||
}, | |||
[createStockInLine], | |||
[setStockInLine], | |||
); | |||
const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { | |||
return await fetchQcResult(stockInLineId as number); | |||
@@ -217,7 +253,7 @@ function PoInputGrid({ | |||
setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[fetchQcDefaultValue], | |||
[fetchQcDefaultValue, openQcModal], | |||
); | |||
const handleEscalation = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
@@ -234,7 +270,7 @@ function PoInputGrid({ | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[], | |||
[openEscalationModal], | |||
); | |||
const handleReject = useCallback( | |||
@@ -254,7 +290,7 @@ function PoInputGrid({ | |||
// printQrcode(params.row); | |||
}, 200); | |||
}, | |||
[], | |||
[openRejectModal], | |||
); | |||
const handleStockIn = useCallback( | |||
@@ -274,7 +310,7 @@ function PoInputGrid({ | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[], | |||
[openStockInModal], | |||
); | |||
const handlePutAway = useCallback( | |||
@@ -294,7 +330,7 @@ function PoInputGrid({ | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[], | |||
[openPutAwayModal], | |||
); | |||
const printQrcode = useCallback( | |||
@@ -310,79 +346,47 @@ function PoInputGrid({ | |||
} | |||
setBtnIsLoading(false); | |||
}, | |||
[fetchPoQrcode, downloadFile], | |||
); | |||
const handleQrCode = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
setModalInfo(params.row); | |||
setTimeout(() => { | |||
// open stock in modal | |||
// openPutAwayModal(); | |||
// return the record with its status as pending | |||
// update layout | |||
console.log("delayed"); | |||
printQrcode(params.row); | |||
}, 200); | |||
}, | |||
[], | |||
); | |||
const closeQcModal = useCallback(() => { | |||
setQcOpen(false); | |||
}, []); | |||
const openQcModal = useCallback(() => { | |||
setQcOpen(true); | |||
}, []); | |||
const closeStockInModal = useCallback(() => { | |||
setStockInOpen(false); | |||
}, []); | |||
const openStockInModal = useCallback(() => { | |||
setStockInOpen(true); | |||
}, []); | |||
const closePutAwayModal = useCallback(() => { | |||
setPutAwayOpen(false); | |||
}, []); | |||
const openPutAwayModal = useCallback(() => { | |||
setPutAwayOpen(true); | |||
}, []); | |||
const closeEscalationModal = useCallback(() => { | |||
setEscalOpen(false); | |||
}, []); | |||
const openEscalationModal = useCallback(() => { | |||
setEscalOpen(true); | |||
}, []); | |||
const closeRejectModal = useCallback(() => { | |||
setRejectOpen(false); | |||
}, []); | |||
const openRejectModal = useCallback(() => { | |||
setRejectOpen(true); | |||
}, []); | |||
// const handleQrCode = useCallback( | |||
// (id: GridRowId, params: any) => () => { | |||
// setRowModesModel((prev) => ({ | |||
// ...prev, | |||
// [id]: { mode: GridRowModes.View }, | |||
// })); | |||
// setModalInfo(params.row); | |||
// setTimeout(() => { | |||
// // open stock in modal | |||
// // openPutAwayModal(); | |||
// // return the record with its status as pending | |||
// // update layout | |||
// console.log("delayed"); | |||
// printQrcode(params.row); | |||
// }, 200); | |||
// }, | |||
// [printQrcode], | |||
// ); | |||
const columns = useMemo<GridColDef[]>( | |||
() => [ | |||
{ | |||
field: "itemNo", | |||
headerName: t("itemNo"), | |||
flex: 0.4, | |||
width: 120, | |||
// flex: 0.4, | |||
}, | |||
{ | |||
field: "itemName", | |||
headerName: t("itemName"), | |||
flex: 0.6, | |||
width: 120, | |||
// flex: 0.6, | |||
}, | |||
{ | |||
field: "acceptedQty", | |||
headerName: t("acceptedQty"), | |||
flex: 0.5, | |||
// flex: 0.5, | |||
width: 120, | |||
type: "number", | |||
// editable: true, | |||
// replace with tooltip + content | |||
@@ -390,7 +394,8 @@ function PoInputGrid({ | |||
{ | |||
field: "uom", | |||
headerName: t("uom"), | |||
flex: 0.5, | |||
width: 120, | |||
// flex: 0.5, | |||
renderCell: (params) => { | |||
return params.row.uom.code; | |||
}, | |||
@@ -398,7 +403,8 @@ function PoInputGrid({ | |||
{ | |||
field: "weight", | |||
headerName: t("weight"), | |||
flex: 0.5, | |||
width: 120, | |||
// flex: 0.5, | |||
renderCell: (params) => { | |||
const weight = calculateWeight( | |||
params.row.acceptedQty, | |||
@@ -411,7 +417,8 @@ function PoInputGrid({ | |||
{ | |||
field: "status", | |||
headerName: t("status"), | |||
flex: 0.5, | |||
width: 120, | |||
// flex: 0.5, | |||
renderCell: (params) => { | |||
return t(`${params.row.status}`); | |||
}, | |||
@@ -423,7 +430,8 @@ function PoInputGrid({ | |||
"stock in", | |||
)} | ${t("putaway")} | ${t("delete")}`, | |||
// headerName: "start | qc | escalation | stock in | putaway | delete", | |||
flex: 1.5, | |||
width: 300, | |||
// flex: 1.5, | |||
cellClassName: "actions", | |||
getActions: (params) => { | |||
// console.log(params.row.status); | |||
@@ -494,7 +502,7 @@ function PoInputGrid({ | |||
(stockInLineStatusMap[status] >= 3 && | |||
stockInLineStatusMap[status] <= 5 && | |||
!session?.user?.abilities?.includes("APPROVAL")) | |||
} | |||
} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleStockIn(params.row.id, params)} | |||
@@ -560,7 +568,7 @@ function PoInputGrid({ | |||
}, | |||
}, | |||
], | |||
[stockInLineStatusMap, btnIsLoading, handleQrCode, handleReject], | |||
[t, handleStart, handleQC, handleEscalation, session?.user?.abilities, handleStockIn, handlePutAway, handleDelete, handleReject], | |||
); | |||
const addRow = useCallback(() => { | |||
@@ -585,7 +593,7 @@ function PoInputGrid({ | |||
// fieldToFocus: "projectId", | |||
}, | |||
})); | |||
}, [currQty, getRowId]); | |||
}, [currQty, getRowId, itemDetail]); | |||
const validation = useCallback( | |||
( | |||
newRow: GridRowModel<StockInLineRow>, | |||
@@ -599,7 +607,7 @@ function PoInputGrid({ | |||
} | |||
return Object.keys(error).length > 0 ? error : undefined; | |||
}, | |||
[currQty], | |||
[currQty, itemDetail.qty, t], | |||
); | |||
const processRowUpdate = useCallback( | |||
( | |||
@@ -632,7 +640,7 @@ function PoInputGrid({ | |||
setCurrQty(total); | |||
return rowToSave; | |||
}, | |||
[getRowId, entries], | |||
[validation, entries, setStockInLine, getRowId], | |||
); | |||
const onProcessRowUpdateError = useCallback( | |||
@@ -27,7 +27,7 @@ | |||
"total weight": "總重量", | |||
"weight unit": "重量單位", | |||
"price": "價格", | |||
"processed": "已入倉", | |||
"processed": "已處理", | |||
"expiryDate": "到期日", | |||
"acceptedQty": "接受數量", | |||
"weight": "重量", | |||