@@ -13,7 +13,7 @@ import { UploadProvider } from "@/components/UploadProvider/UploadProvider"; | |||
import SessionProviderWrapper from "@/components/SessionProviderWrapper/SessionProviderWrapper"; | |||
import QrCodeScannerProvider from "@/components/QrCodeScannerProvider/QrCodeScannerProvider"; | |||
import { I18nProvider } from "@/i18n"; | |||
import "src/app/global.css" | |||
export default async function MainLayout({ | |||
children, | |||
}: { | |||
@@ -39,6 +39,7 @@ export interface InventoryLotLineResult { | |||
uom: string; | |||
qtyPerSmallestUnit: number; | |||
baseUom: string; | |||
stockInLineId: number | |||
} | |||
export interface InventoryLotLineItem { | |||
@@ -38,8 +38,10 @@ export interface PurchaseQcResult { | |||
} | |||
export interface StockInInput { | |||
status: string; | |||
poCode: string; | |||
productLotNo?: string; | |||
dnNo?: string; | |||
itemName: string; | |||
invoiceNo?: string; | |||
receiptDate: string; | |||
acceptedQty: number; | |||
@@ -1,3 +1,7 @@ | |||
@tailwind components; | |||
@tailwind utilities; | |||
@tailwind utilities; | |||
html, body { | |||
overscroll-behavior: none; | |||
} |
@@ -160,6 +160,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||
type: "input-number", | |||
style: { | |||
textAlign: "right", | |||
// width: "100px", | |||
}, | |||
renderCell: (row) => { | |||
if (typeof row.demandQty == "number") { | |||
@@ -174,6 +175,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||
type: "read-only", | |||
style: { | |||
textAlign: "left", | |||
// width: "100px", | |||
}, | |||
renderCell: (row) => { | |||
return row.uomName; | |||
@@ -185,6 +187,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||
type: "read-only", | |||
style: { | |||
textAlign: "right", | |||
// width: "100px", | |||
}, | |||
renderCell: (row) => { | |||
return <ProdTimeColumn prodTimeInMinute={row.prodTimeInMinute} /> | |||
@@ -196,6 +199,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||
type: "read-only", | |||
style: { | |||
textAlign: "right", | |||
// width: "100px", | |||
}, | |||
// editable: true, | |||
}, | |||
@@ -84,9 +84,15 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
const [rows, setRows] = useState<PurchaseOrderLine[]>( | |||
purchaseOrder.pol || [], | |||
); | |||
const params = useSearchParams(); | |||
const searchParams = useSearchParams(); | |||
// const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status); | |||
const removeParam = (paramToRemove: string) => { | |||
const newParams = new URLSearchParams(searchParams.toString()); | |||
newParams.delete(paramToRemove); | |||
window.history.replaceState({}, '', `${window.location.pathname}?${newParams}`); | |||
}; | |||
const handleCompletePo = useCallback(async () => { | |||
const checkRes = await checkPolAndCompletePo(purchaseOrder.id); | |||
console.log(checkRes); | |||
@@ -107,6 +113,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
function Row(props: { row: PurchaseOrderLine }) { | |||
const { row } = props; | |||
const [firstReceiveQty, setFirstReceiveQty] = useState<number>() | |||
const [secondReceiveQty, setSecondReceiveQty] = useState<number>() | |||
const [open, setOpen] = useState(false); | |||
const [processedQty, setProcessedQty] = useState(row.processed); | |||
const [currStatus, setCurrStatus] = useState(row.status); | |||
@@ -155,11 +163,30 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
<TableCell align="left">{row.price}</TableCell> | |||
{/* <TableCell align="left">{row.expiryDate}</TableCell> */} | |||
<TableCell align="left">{t(`${currStatus.toLowerCase()}`)}</TableCell> | |||
{/* <TableCell align="left"> | |||
0 | |||
</TableCell> | |||
<TableCell align="left"> | |||
<TextField | |||
label="輸入數量" | |||
type="text" // Use type="text" to allow validation in the change handler | |||
variant="outlined" | |||
value={secondReceiveQty} | |||
// onChange={handleChange} | |||
InputProps={{ | |||
inputProps: { | |||
min: 0, // Optional: set a minimum value | |||
step: 1 // Optional: set the step for the number input | |||
} | |||
}} | |||
/> | |||
</TableCell> */} | |||
</TableRow> | |||
<TableRow> | |||
{/* <TableCell /> */} | |||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={12}> | |||
<Collapse in={open} timeout="auto" unmountOnExit> | |||
<Collapse in={true} timeout="auto" unmountOnExit> | |||
{/* <Collapse in={open} timeout="auto" unmountOnExit> */} | |||
<Table> | |||
<TableBody> | |||
<TableRow> | |||
@@ -260,7 +287,21 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
} | |||
}, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); | |||
console.log(window.innerWidth) | |||
const FIRST_IN_FIELD = "firstInQty" | |||
const SECOND_IN_FIELD = "secondInQty" | |||
const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => { | |||
switch (field) { | |||
case FIRST_IN_FIELD: | |||
return true; | |||
case SECOND_IN_FIELD: | |||
return true; | |||
default: | |||
return false; // Default case | |||
} | |||
}, []); | |||
return ( | |||
<> | |||
<Stack | |||
@@ -277,18 +318,56 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
</Typography> | |||
</Grid> | |||
</Grid> | |||
<Grid container xs={12} justifyContent="start"> | |||
<Grid item> | |||
<Button | |||
{false ? (<Grid container xs={12} justifyContent="start"> | |||
<Grid item xs={3}> | |||
<TextField | |||
label={t("dnNo")} | |||
type="text" // Use type="text" to allow validation in the change handler | |||
variant="outlined" | |||
// value={secondReceiveQty} | |||
// onChange={handleChange} | |||
InputProps={{ | |||
inputProps: { | |||
min: 0, // Optional: set a minimum value | |||
step: 1 // Optional: set the step for the number input | |||
} | |||
}} | |||
/> | |||
</Grid> | |||
<Grid item xs={3}> | |||
<TextField | |||
label={t("dnDate")} | |||
type="text" // Use type="text" to allow validation in the change handler | |||
variant="outlined" | |||
defaultValue={"07/08/2025"} | |||
// value={secondReceiveQty} | |||
// onChange={handleChange} | |||
InputProps={{ | |||
inputProps: { | |||
min: 0, // Optional: set a minimum value | |||
step: 1 // Optional: set the step for the number input | |||
} | |||
}} | |||
/> | |||
{/* <Button | |||
onClick={buttonData.onClick} | |||
disabled={buttonData.disabled} | |||
color={buttonData.buttonColor as ButtonProps["color"]} | |||
startIcon={buttonData.buttonIcon} | |||
> | |||
{buttonData.buttonText} | |||
</Button> | |||
</Button> */} | |||
</Grid> | |||
</Grid> | |||
<Grid | |||
item | |||
xs={6} | |||
display="flex" | |||
justifyContent="end" | |||
alignItems="end" | |||
> | |||
<Button onClick={onOpenScanner}>{t("Accept submit")}</Button> | |||
</Grid> | |||
</Grid>) : undefined} | |||
<Grid container xs={12} justifyContent="space-between"> | |||
<Grid item xs={8}> | |||
<Tabs | |||
@@ -296,7 +375,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
onChange={handleTabChange} | |||
variant="scrollable" | |||
> | |||
<Tab label={t("General")} iconPosition="end" /> | |||
{/* <Tab label={t("General")} iconPosition="end" /> */} | |||
{/* <Tab label={t("Bind Storage")} iconPosition="end" /> */} | |||
</Tabs> | |||
</Grid> | |||
@@ -315,7 +394,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
onClose={onCloseScanner} | |||
warehouse={warehouse} | |||
/> | |||
<Button onClick={onOpenScanner}>{t("bind")}</Button> | |||
{/* <Button onClick={onOpenScanner}>{t("Accept submit")}</Button> */} | |||
{/* <Button onClick={onOpenScanner}>{t("bind")}</Button> */} | |||
</Grid> | |||
</Grid> | |||
{/* tab 1 */} | |||
@@ -336,6 +416,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
<TableCell align="left">{t("price")}</TableCell> | |||
{/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | |||
<TableCell align="left">{t("status")}</TableCell> | |||
{/* start == true && firstInQty == null ? no hide : hide*/} | |||
{/* {renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="left">{t("receivedQty")}</TableCell> : undefined} */} | |||
{/* start == true && firstInQty == null ? hide and disabled : no hide*/} | |||
{/* {renderFieldCondition(SECOND_IN_FIELD) ? <TableCell align="left">{t("dnQty")}</TableCell> : undefined} */} | |||
{/* <TableCell align="left">{"add icon button"}</TableCell> */} | |||
</TableRow> | |||
</TableHead> | |||
@@ -57,6 +57,7 @@ import { fetchQcResult } from "@/app/api/qc/actions"; | |||
import PoQcStockInModal from "./PoQcStockInModal"; | |||
import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | |||
import { useSession } from "next-auth/react"; | |||
import PoQcStockInModalVer2 from "./QcStockInModalVer2"; | |||
interface ResultWithId { | |||
id: number; | |||
@@ -255,6 +256,40 @@ function PoInputGrid({ | |||
}, | |||
[fetchQcDefaultValue, openQcModal], | |||
); | |||
const [newOpen, setNewOpen] = useState(false); | |||
const closeNewModal = useCallback(() => { | |||
setNewOpen(false); | |||
}, []); | |||
const openNewModal = useCallback(() => { | |||
setNewOpen(true); | |||
}, []); | |||
const handleNewQC = useCallback( | |||
(id: GridRowId, params: any) => async () => { | |||
setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
const qcResult = await fetchQcDefaultValue(id); | |||
console.log(params.row); | |||
console.log(qcResult); | |||
setModalInfo({ | |||
...params.row, | |||
qcResult: qcResult, | |||
}); | |||
// set default values | |||
setTimeout(() => { | |||
// open qc modal | |||
console.log("delayed"); | |||
openNewModal(); | |||
setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[fetchQcDefaultValue, openNewModal], | |||
); | |||
const handleEscalation = useCallback( | |||
(id: GridRowId, params: any) => () => { | |||
// setBtnIsLoading(true); | |||
@@ -373,20 +408,38 @@ function PoInputGrid({ | |||
{ | |||
field: "itemNo", | |||
headerName: t("itemNo"), | |||
width: 120, | |||
width: 100, | |||
// flex: 0.4, | |||
}, | |||
{ | |||
field: "dnNo", | |||
headerName: t("dnNo"), | |||
width: 100, | |||
renderCell: () => { | |||
return <>DN0000001</> | |||
} | |||
// flex: 0.4, | |||
}, | |||
{ | |||
field: "dnDate", | |||
headerName: t("dnDate"), | |||
width: 100, | |||
renderCell: () => { | |||
return <>07/08/2025</> | |||
} | |||
// flex: 0.4, | |||
}, | |||
{ | |||
field: "itemName", | |||
headerName: t("itemName"), | |||
width: 120, | |||
width: 100, | |||
// flex: 0.6, | |||
}, | |||
{ | |||
field: "acceptedQty", | |||
headerName: t("acceptedQty"), | |||
// flex: 0.5, | |||
width: 120, | |||
width: 100, | |||
type: "number", | |||
// editable: true, | |||
// replace with tooltip + content | |||
@@ -417,7 +470,7 @@ function PoInputGrid({ | |||
{ | |||
field: "status", | |||
headerName: t("status"), | |||
width: 120, | |||
width: 70, | |||
// flex: 0.5, | |||
renderCell: (params) => { | |||
return t(`${params.row.status}`); | |||
@@ -426,12 +479,13 @@ function PoInputGrid({ | |||
{ | |||
field: "actions", | |||
type: "actions", | |||
headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t( | |||
"stock in", | |||
)} | ${t("putaway")} | ${t("delete")}`, | |||
// headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t( | |||
// "stock in", | |||
// )} | ${t("putaway")} | ${t("delete")}`, | |||
headerName: "動作", | |||
// headerName: "start | qc | escalation | stock in | putaway | delete", | |||
width: 300, | |||
// flex: 1.5, | |||
// flex: 2, | |||
cellClassName: "actions", | |||
getActions: (params) => { | |||
// console.log(params.row.status); | |||
@@ -440,130 +494,158 @@ function PoInputGrid({ | |||
// console.log(session?.user?.abilities?.includes("APPROVAL")); | |||
return [ | |||
<GridActionsCellItem | |||
icon={<PlayArrowIcon />} | |||
icon={<Button variant="contained">{t("qc processing")}</Button>} | |||
label="start" | |||
sx={{ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={!(stockInLineStatusMap[status] === 0)} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleStart(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
<GridActionsCellItem | |||
icon={<FactCheckIcon />} | |||
label="qc" | |||
sx={{ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={ | |||
// stockInLineStatusMap[status] === 9 || | |||
stockInLineStatusMap[status] < 1 | |||
} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleQC(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
<GridActionsCellItem | |||
icon={<NotificationImportantIcon />} | |||
label="escalation" | |||
sx={{ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={ | |||
stockInLineStatusMap[status] === 9 || | |||
stockInLineStatusMap[status] <= 0 || | |||
stockInLineStatusMap[status] >= 5 | |||
} | |||
// disabled={!(stockInLineStatusMap[status] === 0)} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handleEscalation(params.row.id, params)} | |||
onClick={handleNewQC(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
<GridActionsCellItem | |||
icon={<ShoppingCartIcon />} | |||
label="stockin" | |||
sx={{ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={ | |||
stockInLineStatusMap[status] === 9 || | |||
stockInLineStatusMap[status] <= 2 || | |||
stockInLineStatusMap[status] >= 7 || | |||
(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)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
<GridActionsCellItem | |||
icon={<ShoppingCartIcon />} | |||
label="putaway" | |||
icon={<Button variant="contained">{t("putawayBtn")}</Button>} | |||
label="start" | |||
sx={{ | |||
color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
disabled={ | |||
stockInLineStatusMap[status] === 9 || | |||
stockInLineStatusMap[status] < 7 | |||
} | |||
// disabled={!(stockInLineStatusMap[status] === 0)} | |||
// set _isNew to false after posting | |||
// or check status | |||
onClick={handlePutAway(params.row.id, params)} | |||
onClick={handleStart(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
// <GridActionsCellItem | |||
// icon={<QrCodeIcon />} | |||
// icon={<Button variant="contained">{t("qc processing")}</Button>} | |||
// label="start" | |||
// sx={{ | |||
// color: "primary.main", | |||
// // marginRight: 1, | |||
// }} | |||
// disabled={!(stockInLineStatusMap[status] === 0)} | |||
// // set _isNew to false after posting | |||
// // or check status | |||
// onClick={handleStart(params.row.id, params)} | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
// <GridActionsCellItem | |||
// icon={<FactCheckIcon />} | |||
// label="qc" | |||
// sx={{ | |||
// color: "primary.main", | |||
// // marginRight: 1, | |||
// }} | |||
// disabled={ | |||
// // stockInLineStatusMap[status] === 9 || | |||
// stockInLineStatusMap[status] < 1 | |||
// } | |||
// // set _isNew to false after posting | |||
// // or check status | |||
// onClick={handleQC(params.row.id, params)} | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
// <GridActionsCellItem | |||
// icon={<NotificationImportantIcon />} | |||
// label="escalation" | |||
// sx={{ | |||
// color: "primary.main", | |||
// // marginRight: 1, | |||
// }} | |||
// disabled={ | |||
// stockInLineStatusMap[status] === 9 || | |||
// stockInLineStatusMap[status] <= 0 || | |||
// stockInLineStatusMap[status] >= 5 | |||
// } | |||
// // set _isNew to false after posting | |||
// // or check status | |||
// onClick={handleEscalation(params.row.id, params)} | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
// <GridActionsCellItem | |||
// icon={<ShoppingCartIcon />} | |||
// label="stockin" | |||
// sx={{ | |||
// color: "primary.main", | |||
// // marginRight: 1, | |||
// }} | |||
// disabled={ | |||
// stockInLineStatusMap[status] === 9 || | |||
// stockInLineStatusMap[status] <= 2 || | |||
// stockInLineStatusMap[status] >= 7 || | |||
// (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)} | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
// <GridActionsCellItem | |||
// icon={<ShoppingCartIcon />} | |||
// label="putaway" | |||
// sx={{ | |||
// color: "primary.main", | |||
// // marginRight: 1, | |||
// }} | |||
// disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||
// disabled={ | |||
// stockInLineStatusMap[status] === 9 || | |||
// stockInLineStatusMap[status] < 7 | |||
// } | |||
// // set _isNew to false after posting | |||
// // or check status | |||
// onClick={handleQrCode(params.row.id, params)} | |||
// onClick={handlePutAway(params.row.id, params)} | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
// // <GridActionsCellItem | |||
// // icon={<QrCodeIcon />} | |||
// // label="putaway" | |||
// // sx={{ | |||
// // color: "primary.main", | |||
// // // marginRight: 1, | |||
// // }} | |||
// // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||
// // // set _isNew to false after posting | |||
// // // or check status | |||
// // onClick={handleQrCode(params.row.id, params)} | |||
// // color="inherit" | |||
// // key="edit" | |||
// // />, | |||
// <GridActionsCellItem | |||
// icon={ | |||
// stockInLineStatusMap[status] >= 1 ? ( | |||
// <DoDisturbIcon /> | |||
// ) : ( | |||
// <DeleteIcon /> | |||
// ) | |||
// } | |||
// label="Delete" | |||
// sx={{ | |||
// color: "error.main", | |||
// }} | |||
// disabled={ | |||
// stockInLineStatusMap[status] >= 7 && | |||
// stockInLineStatusMap[status] <= 9 | |||
// } | |||
// onClick={ | |||
// stockInLineStatusMap[status] === 0 | |||
// ? handleDelete(params.row.id) | |||
// : handleReject(params.row.id, params) | |||
// } | |||
// color="inherit" | |||
// key="edit" | |||
// />, | |||
<GridActionsCellItem | |||
icon={ | |||
stockInLineStatusMap[status] >= 1 ? ( | |||
<DoDisturbIcon /> | |||
) : ( | |||
<DeleteIcon /> | |||
) | |||
} | |||
label="Delete" | |||
sx={{ | |||
color: "error.main", | |||
}} | |||
disabled={ | |||
stockInLineStatusMap[status] >= 7 && | |||
stockInLineStatusMap[status] <= 9 | |||
} | |||
onClick={ | |||
stockInLineStatusMap[status] === 0 | |||
? handleDelete(params.row.id) | |||
: handleReject(params.row.id, params) | |||
} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
]; | |||
}, | |||
}, | |||
@@ -594,6 +676,7 @@ function PoInputGrid({ | |||
}, | |||
})); | |||
}, [currQty, getRowId, itemDetail]); | |||
const validation = useCallback( | |||
( | |||
newRow: GridRowModel<StockInLineRow>, | |||
@@ -654,20 +737,22 @@ function PoInputGrid({ | |||
); | |||
const footer = ( | |||
<Box display="flex" gap={2} alignItems="center"> | |||
<Button | |||
disableRipple | |||
variant="outlined" | |||
startIcon={<Add />} | |||
disabled={itemDetail.qty - currQty <= 0} | |||
onClick={addRow} | |||
size="small" | |||
> | |||
{t("Record pol")} | |||
</Button> | |||
</Box> | |||
<> | |||
{/* <Box display="flex" gap={2} alignItems="center"> | |||
<Button | |||
disableRipple | |||
variant="outlined" | |||
startIcon={<Add />} | |||
disabled={itemDetail.qty - currQty <= 0} | |||
onClick={addRow} | |||
size="small" | |||
> | |||
{t("Record pol")} | |||
</Button> | |||
</Box> */} | |||
</> | |||
); | |||
return ( | |||
<> | |||
<StyledDataGrid | |||
@@ -715,6 +800,21 @@ function PoInputGrid({ | |||
footer: { child: footer }, | |||
}} | |||
/> | |||
{modalInfo !== undefined && ( | |||
<> | |||
<PoQcStockInModalVer2 | |||
// setRows={setRows} | |||
setEntries={setEntries} | |||
setStockInLine={setStockInLine} | |||
setItemDetail={setModalInfo} | |||
qc={qc} | |||
open={newOpen} | |||
onClose={closeNewModal} | |||
itemDetail={modalInfo} | |||
/> | |||
</> | |||
) | |||
} | |||
{modalInfo !== undefined && ( | |||
<> | |||
<PoQcStockInModal | |||
@@ -0,0 +1,400 @@ | |||
"use client"; | |||
import { | |||
Dispatch, | |||
MutableRefObject, | |||
SetStateAction, | |||
useCallback, | |||
useEffect, | |||
useMemo, | |||
useState, | |||
} from "react"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { | |||
FooterPropsOverrides, | |||
GridActionsCellItem, | |||
GridCellParams, | |||
GridColDef, | |||
GridEventListener, | |||
GridRowEditStopReasons, | |||
GridRowId, | |||
GridRowIdGetter, | |||
GridRowModel, | |||
GridRowModes, | |||
GridRowModesModel, | |||
GridRowSelectionModel, | |||
GridToolbarContainer, | |||
GridValidRowModel, | |||
useGridApiRef, | |||
} from "@mui/x-data-grid"; | |||
import { set, useFormContext } from "react-hook-form"; | |||
import SaveIcon from "@mui/icons-material/Save"; | |||
import DeleteIcon from "@mui/icons-material/Delete"; | |||
import CancelIcon from "@mui/icons-material/Cancel"; | |||
import { Add } from "@mui/icons-material"; | |||
import { Box, Button, Typography } from "@mui/material"; | |||
import { useTranslation } from "react-i18next"; | |||
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; | |||
} | |||
// export type InputGridProps = { | |||
// [key: string]: any | |||
// } | |||
interface DefaultResult<E> { | |||
_isNew: boolean; | |||
_error: E; | |||
} | |||
interface SelectionResult<E> { | |||
active: boolean; | |||
_isNew: boolean; | |||
_error: E; | |||
} | |||
type Result<E> = DefaultResult<E> | SelectionResult<E>; | |||
export type TableRow<V, E> = Partial< | |||
V & { | |||
isActive: boolean | undefined; | |||
_isNew: boolean; | |||
_error: E; | |||
} & ResultWithId | |||
>; | |||
export interface InputDataGridProps<T, V, E> { | |||
apiRef: MutableRefObject<GridApiCommunity>; | |||
checkboxSelection: false | undefined; | |||
_formKey: keyof T; | |||
columns: GridColDef[]; | |||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
needAdd?: boolean; | |||
} | |||
export interface SelectionInputDataGridProps<T, V, E> { | |||
// thinking how do | |||
apiRef: MutableRefObject<GridApiCommunity>; | |||
checkboxSelection: true; | |||
_formKey: keyof T; | |||
columns: GridColDef[]; | |||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
needAdd?: boolean; | |||
} | |||
export type Props<T, V, E> = | |||
| InputDataGridProps<T, V, E> | |||
| SelectionInputDataGridProps<T, V, E>; | |||
export class ProcessRowUpdateError<T, E> extends Error { | |||
public readonly row: T; | |||
public readonly errors: E | undefined; | |||
constructor(row: T, message?: string, errors?: E) { | |||
super(message); | |||
this.row = row; | |||
this.errors = errors; | |||
Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||
} | |||
} | |||
// T == CreatexxxInputs map of the form's fields | |||
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||
// E == error | |||
function QcDatagrid<T, V, E>({ | |||
apiRef, | |||
checkboxSelection = false, | |||
_formKey, | |||
columns, | |||
validateRow, | |||
needAdd, | |||
}: Props<T, V, E>) { | |||
const { | |||
t, | |||
// i18n: { language }, | |||
} = useTranslation("common"); | |||
const formKey = _formKey.toString(); | |||
const { setValue, getValues } = useFormContext(); | |||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||
// const apiRef = useGridApiRef(); | |||
const getRowId = useCallback<GridRowIdGetter<TableRow<V, E>>>( | |||
(row) => row.id! as number, | |||
[], | |||
); | |||
const list: TableRow<V, E>[] = getValues(formKey); | |||
// console.log(list) | |||
const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | |||
const list: TableRow<V, E>[] = getValues(formKey); | |||
return 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>(() => { | |||
// const rowModel = list.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | |||
const rowModel: GridRowSelectionModel = getValues( | |||
`${formKey}_active`, | |||
) as GridRowSelectionModel; | |||
console.log(rowModel); | |||
return rowModel; | |||
}); | |||
const handleSave = useCallback( | |||
(id: GridRowId) => () => { | |||
setRowModesModel((prevRowModesModel) => ({ | |||
...prevRowModesModel, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
}, | |||
[], | |||
); | |||
const onProcessRowUpdateError = useCallback( | |||
(updateError: ProcessRowUpdateError<T, E>) => { | |||
const errors = updateError.errors; | |||
const row = updateError.row; | |||
console.log(errors); | |||
apiRef.current.updateRows([{ ...row, _error: errors }]); | |||
}, | |||
[apiRef], | |||
); | |||
const processRowUpdate = useCallback( | |||
( | |||
newRow: GridRowModel<TableRow<V, E>>, | |||
originalRow: GridRowModel<TableRow<V, E>>, | |||
) => { | |||
///////////////// | |||
// validation here | |||
const errors = validateRow(newRow); | |||
console.log(newRow); | |||
if (errors) { | |||
throw new ProcessRowUpdateError( | |||
originalRow, | |||
"validation error", | |||
errors, | |||
); | |||
} | |||
///////////////// | |||
const { _isNew, _error, ...updatedRow } = newRow; | |||
const rowToSave = { | |||
...updatedRow, | |||
} as TableRow<V, E>; /// test | |||
console.log(rowToSave); | |||
setRows((rw) => | |||
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)), | |||
); | |||
return rowToSave; | |||
}, | |||
[validateRow, getRowId], | |||
); | |||
const addRow = useCallback(() => { | |||
const newEntry = { id: Date.now(), _isNew: true } as TableRow<V, E>; | |||
setRows((prev) => [...prev, newEntry]); | |||
setRowModesModel((model) => ({ | |||
...model, | |||
[getRowId(newEntry)]: { | |||
mode: GridRowModes.Edit, | |||
// fieldToFocus: "team", /// test | |||
}, | |||
})); | |||
}, [getRowId]); | |||
const reset = useCallback(() => { | |||
setRowModesModel({}); | |||
setRows(originalRows); | |||
}, [originalRows]); | |||
const handleCancel = useCallback( | |||
(id: GridRowId) => () => { | |||
setRowModesModel((model) => ({ | |||
...model, | |||
[id]: { mode: GridRowModes.View, ignoreModifications: true }, | |||
})); | |||
const editedRow = rows.find((row) => getRowId(row) === id); | |||
if (editedRow?._isNew) { | |||
setRows((rw) => rw.filter((r) => getRowId(r) !== id)); | |||
} else { | |||
setRows((rw) => | |||
rw.map((r) => (getRowId(r) === id ? { ...r, _error: undefined } : r)), | |||
); | |||
} | |||
}, | |||
[rows, getRowId], | |||
); | |||
const handleDelete = useCallback( | |||
(id: GridRowId) => () => { | |||
setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | |||
}, | |||
[getRowId], | |||
); | |||
const _columns = useMemo<GridColDef[]>( | |||
() => [ | |||
...columns, | |||
{ | |||
field: "actions", | |||
type: "actions", | |||
headerName: "", | |||
flex: 0.5, | |||
cellClassName: "actions", | |||
getActions: ({ id }: { id: GridRowId }) => { | |||
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; | |||
if (isInEditMode) { | |||
return [ | |||
<GridActionsCellItem | |||
icon={<SaveIcon />} | |||
label="Save" | |||
key="edit" | |||
sx={{ | |||
color: "primary.main", | |||
}} | |||
onClick={handleSave(id)} | |||
/>, | |||
<GridActionsCellItem | |||
icon={<CancelIcon />} | |||
label="Cancel" | |||
key="edit" | |||
onClick={handleCancel(id)} | |||
/>, | |||
]; | |||
} | |||
return [ | |||
<GridActionsCellItem | |||
icon={<DeleteIcon />} | |||
label="Delete" | |||
sx={{ | |||
color: "error.main", | |||
}} | |||
onClick={handleDelete(id)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
]; | |||
}, | |||
}, | |||
], | |||
[columns, rowModesModel, handleSave, handleCancel, handleDelete], | |||
); | |||
// sync useForm | |||
useEffect(() => { | |||
// console.log(formKey) | |||
// console.log(rows) | |||
setValue(formKey, rows); | |||
}, [formKey, rows, setValue]); | |||
const footer = ( | |||
<Box display="flex" gap={2} alignItems="center"> | |||
<Button | |||
disableRipple | |||
variant="outlined" | |||
startIcon={<Add />} | |||
onClick={addRow} | |||
size="small" | |||
> | |||
{t("Add Record")} | |||
</Button> | |||
<Button | |||
disableRipple | |||
variant="outlined" | |||
startIcon={<Add />} | |||
onClick={reset} | |||
size="small" | |||
> | |||
{t("Clean Record")} | |||
</Button> | |||
</Box> | |||
); | |||
// const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | |||
// if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||
// event.defaultMuiPrevented = true; | |||
// } | |||
// }; | |||
return ( | |||
<StyledDataGrid | |||
// {...props} | |||
// getRowId={getRowId as GridRowIdGetter<GridValidRowModel>} | |||
// checkbox selection | |||
checkboxSelection={checkboxSelection} | |||
disableRowSelectionOnClick={checkboxSelection} | |||
onRowSelectionModelChange={(newRowSelectionModel) => { | |||
if (checkboxSelection) { | |||
setRowSelectionModel(newRowSelectionModel); | |||
setValue("qcChecks_active", newRowSelectionModel); | |||
} | |||
}} | |||
rowSelectionModel={rowSelectionModel} | |||
apiRef={apiRef} | |||
rows={rows} | |||
columns={!checkboxSelection ? _columns : columns} | |||
editMode="row" | |||
autoHeight | |||
sx={{ | |||
"--DataGrid-overlayHeight": "100px", | |||
".MuiDataGrid-row .MuiDataGrid-cell.hasError": { | |||
border: "1px solid", | |||
borderColor: "error.main", | |||
}, | |||
".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": { | |||
border: "1px solid", | |||
borderColor: "warning.main", | |||
}, | |||
}} | |||
disableColumnMenu | |||
processRowUpdate={processRowUpdate as any} | |||
// onRowEditStop={handleRowEditStop} | |||
rowModesModel={rowModesModel} | |||
onRowModesModelChange={setRowModesModel} | |||
onProcessRowUpdateError={onProcessRowUpdateError} | |||
getCellClassName={(params: GridCellParams<TableRow<T, E>>) => { | |||
let classname = ""; | |||
if (params.row._error) { | |||
classname = "hasError"; | |||
} | |||
return classname; | |||
}} | |||
slots={ | |||
!checkboxSelection | |||
? { | |||
footer: FooterToolbar, | |||
noRowsOverlay: NoRowsOverlay, | |||
} | |||
: undefined | |||
} | |||
slotProps={ | |||
!checkboxSelection && Boolean(needAdd) | |||
? { | |||
footer: { child: footer }, | |||
} | |||
: undefined | |||
// slotProps={renderFooter ? { | |||
// footer: { child: footer }, | |||
// }: undefined | |||
} | |||
/> | |||
); | |||
} | |||
const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => { | |||
return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | |||
}; | |||
const NoRowsOverlay: React.FC = () => { | |||
const { t } = useTranslation("home"); | |||
return ( | |||
<Box | |||
display="flex" | |||
justifyContent="center" | |||
alignItems="center" | |||
height="100%" | |||
> | |||
<Typography variant="caption">{t("Add some entries!")}</Typography> | |||
</Box> | |||
); | |||
}; | |||
export default QcDatagrid; |
@@ -0,0 +1,242 @@ | |||
"use client"; | |||
import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; | |||
import { | |||
Box, | |||
Card, | |||
CardContent, | |||
Grid, | |||
Stack, | |||
Tab, | |||
Tabs, | |||
TabsProps, | |||
TextField, | |||
Tooltip, | |||
Typography, | |||
} from "@mui/material"; | |||
import { useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
GridRowModel, | |||
useGridApiContext, | |||
GridRenderCellParams, | |||
GridRenderEditCellParams, | |||
useGridApiRef, | |||
} from "@mui/x-data-grid"; | |||
import InputDataGrid from "../InputDataGrid"; | |||
import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
import TwoLineCell from "./TwoLineCell"; | |||
import QcSelect from "./QcSelect"; | |||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import axios from "@/app/(main)/axios/axiosInstance"; | |||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
qc: QcItemWithChecks[]; | |||
disabled: boolean; | |||
} | |||
type EntryError = | |||
| { | |||
[field in keyof PurchaseQcResult]?: string; | |||
} | |||
| undefined; | |||
type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
// fetchQcItemCheck | |||
const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
const { t } = useTranslation("purchaseOrder"); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
register, | |||
formState: { errors, defaultValues, touchedFields }, | |||
watch, | |||
control, | |||
setValue, | |||
getValues, | |||
reset, | |||
resetField, | |||
setError, | |||
clearErrors, | |||
} = useFormContext<PurchaseQCInput>(); | |||
console.log(itemDetail); | |||
console.log(defaultValues); | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
(_e, newValue) => { | |||
setTabIndex(newValue); | |||
}, | |||
[], | |||
); | |||
//// validate form | |||
const accQty = watch("acceptedQty"); | |||
const validateForm = useCallback(() => { | |||
console.log(accQty); | |||
if (accQty > itemDetail.acceptedQty) { | |||
setError("acceptedQty", { | |||
message: `${t("acceptedQty must not greater than")} ${ | |||
itemDetail.acceptedQty | |||
}`, | |||
type: "required", | |||
}); | |||
} | |||
if (accQty < 1) { | |||
setError("acceptedQty", { | |||
message: t("minimal value is 1"), | |||
type: "required", | |||
}); | |||
} | |||
if (isNaN(accQty)) { | |||
setError("acceptedQty", { | |||
message: t("value must be a number"), | |||
type: "required", | |||
}); | |||
} | |||
}, [accQty]); | |||
useEffect(() => { | |||
clearErrors(); | |||
validateForm(); | |||
}, [clearErrors, validateForm]); | |||
const columns = useMemo<GridColDef[]>( | |||
() => [ | |||
{ | |||
field: "qcItemId", | |||
headerName: t("qc Check"), | |||
flex: 1, | |||
editable: !disabled, | |||
valueFormatter(params) { | |||
const row = params.id ? params.api.getRow<PoQcRow>(params.id) : null; | |||
if (!row) { | |||
return null; | |||
} | |||
const Qc = qc.find((q) => q.id === row.qcItemId); | |||
return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||
}, | |||
renderCell(params: GridRenderCellParams<PoQcRow, number>) { | |||
console.log(params.value); | |||
return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||
}, | |||
renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||
const errorMessage = | |||
params.row._error?.[params.field as keyof PurchaseQcResult]; | |||
console.log(errorMessage); | |||
const content = ( | |||
<QcSelect | |||
allQcs={qc} | |||
value={params.row.qcItemId} | |||
onQcSelect={async (qcItemId) => { | |||
await params.api.setEditCellValue({ | |||
id: params.id, | |||
field: "qcItemId", | |||
value: qcItemId, | |||
}); | |||
// await params.api.setEditCellValue({ | |||
// id: params.id, | |||
// field: "type", | |||
// value: "determine1", | |||
// }); | |||
}} | |||
/> | |||
); | |||
return errorMessage ? ( | |||
<Tooltip title={errorMessage}> | |||
<Box width="100%">{content}</Box> | |||
</Tooltip> | |||
) : ( | |||
content | |||
); | |||
}, | |||
}, | |||
{ | |||
field: "failQty", | |||
headerName: t("failQty"), | |||
flex: 1, | |||
editable: !disabled, | |||
type: "number", | |||
renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||
// const recordQty = params.row.qty | |||
// if (recordQty !== undefined) { | |||
// setUnrecordQty((prev) => prev - recordQty) | |||
// } | |||
const errorMessage = | |||
params.row._error?.[params.field as keyof PurchaseQcResult]; | |||
const content = <GridEditInputCell {...params} />; | |||
return errorMessage ? ( | |||
<Tooltip title={t(errorMessage)}> | |||
<Box width="100%">{content}</Box> | |||
</Tooltip> | |||
) : ( | |||
content | |||
); | |||
}, | |||
}, | |||
], | |||
[qc], | |||
); | |||
/// validate datagrid | |||
const validation = useCallback( | |||
(newRow: GridRowModel<PoQcRow>): EntryError => { | |||
const error: EntryError = {}; | |||
const { qcItemId, failQty } = newRow; | |||
if (!qcItemId || qcItemId <= 0) { | |||
error["qcItemId"] = t("select qc"); | |||
} | |||
if (!failQty || failQty <= 0) { | |||
error["failQty"] = t("enter a failQty"); | |||
} | |||
if (failQty && failQty > itemDetail.acceptedQty) { | |||
error["failQty"] = t("qty too big"); | |||
} | |||
return Object.keys(error).length > 0 ? error : undefined; | |||
}, | |||
[], | |||
); | |||
useEffect(() => { | |||
console.log(itemDetail); | |||
const status = "receiving"; | |||
// switch (itemDetail.status) { | |||
// case 'pending': | |||
// status = "receiving" | |||
// break; | |||
// } | |||
setValue("status", status); | |||
}, [itemDetail]); | |||
return ( | |||
<> | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid | |||
container | |||
justifyContent="flex-start" | |||
alignItems="flex-start" | |||
spacing={2} | |||
sx={{ mt: 0.5 }} | |||
> | |||
<Tabs | |||
value={tabIndex} | |||
onChange={handleTabChange} | |||
variant="scrollable" | |||
> | |||
<Tab label={t("QC Info")} iconPosition="end" /> | |||
<Tab label={t("Escalation History")} iconPosition="end" /> | |||
</Tabs> | |||
</Grid> | |||
</Grid> | |||
</> | |||
); | |||
}; | |||
export default QcFormVer2; |
@@ -0,0 +1,149 @@ | |||
"use client"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { ModalFormInput, PurchaseQcResult } from "@/app/api/po/actions"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { Box, Button, Grid, Modal, ModalProps, Stack, Typography } from "@mui/material"; | |||
import { Dispatch, SetStateAction, useCallback, useState } from "react"; | |||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
import { StockInLineRow } from "./PoInputGrid"; | |||
import { useTranslation } from "react-i18next"; | |||
import StockInForm from "./StockInForm"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import QcFormVer2 from "./QcFormVer2"; | |||
const style = { | |||
position: "absolute", | |||
top: "50%", | |||
left: "50%", | |||
transform: "translate(-50%, -50%)", | |||
overflowY: "scroll", | |||
bgcolor: "background.paper", | |||
pt: 5, | |||
px: 5, | |||
pb: 10, | |||
display: "block", | |||
width: { xs: "60%", sm: "60%", md: "60%" }, | |||
}; | |||
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 | |||
> | |||
>; | |||
qc?: QcItemWithChecks[]; | |||
warehouse?: any[]; | |||
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||
} | |||
interface Props extends CommonProps{ | |||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
} | |||
const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
// type, | |||
// setRows, | |||
setEntries, | |||
setStockInLine, | |||
open, | |||
onClose, | |||
itemDetail, | |||
setItemDetail, | |||
qc, | |||
warehouse, | |||
}) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
} = useTranslation("purchaseOrder"); | |||
const formProps = useForm<ModalFormInput>({ | |||
defaultValues: { | |||
...itemDetail, | |||
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||
// warehouseId: itemDetail.defaultWarehouseId || 0 | |||
}, | |||
}); | |||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
(...args) => { | |||
onClose?.(...args); | |||
// reset(); | |||
}, | |||
[onClose], | |||
); | |||
const [submissionType, setSubmissionType] = useState<"stockIn" | "qc" | "escalate" | undefined>(undefined) | |||
const onSubmit = useCallback<SubmitHandler<ModalFormInput>>( | |||
async (data, event) => { | |||
console.log(event!.nativeEvent) | |||
// divide 3 section for this submition | |||
// switch (submissionType) { | |||
// submit stock in data | |||
// submit qc data | |||
// submit putaway | |||
// } | |||
}, [submissionType]) | |||
return ( | |||
<> | |||
<FormProvider {...formProps}> | |||
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}> | |||
<Box | |||
sx={style} | |||
component="form" | |||
onSubmit={formProps.handleSubmit(onSubmit)} | |||
> | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||
{t("qc processing")} | |||
</Typography> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<StockInFormVer2 | |||
itemDetail={itemDetail} | |||
disabled={false} | |||
/> | |||
</Grid> | |||
</Grid> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
id="stockIn" | |||
type="button" | |||
variant="contained" | |||
color="primary" | |||
> | |||
{t("submitStockIn")} | |||
</Button> | |||
</Stack> | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<QcFormVer2 | |||
qc={qc!} | |||
itemDetail={itemDetail} | |||
disabled={false} | |||
/> | |||
</Grid> | |||
<Button | |||
id="qc" | |||
type="button" | |||
variant="contained" | |||
color="secondary" | |||
> | |||
Submit QC | |||
</Button> | |||
</Box> | |||
</Modal> | |||
</FormProvider> | |||
</> | |||
) | |||
} | |||
export default PoQcStockInModalVer2 |
@@ -0,0 +1,323 @@ | |||
"use client"; | |||
import { | |||
PurchaseQcResult, | |||
PurchaseQCInput, | |||
StockInInput, | |||
} from "@/app/api/po/actions"; | |||
import { | |||
Box, | |||
Card, | |||
CardContent, | |||
Grid, | |||
Stack, | |||
TextField, | |||
Tooltip, | |||
Typography, | |||
} from "@mui/material"; | |||
import { Controller, useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useEffect, useMemo } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
GridRowModel, | |||
useGridApiContext, | |||
GridRenderCellParams, | |||
GridRenderEditCellParams, | |||
useGridApiRef, | |||
} from "@mui/x-data-grid"; | |||
import InputDataGrid from "../InputDataGrid"; | |||
import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
import TwoLineCell from "./TwoLineCell"; | |||
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; | |||
// qc: QcItemWithChecks[]; | |||
disabled: boolean; | |||
} | |||
type EntryError = | |||
| { | |||
[field in keyof StockInInput]?: string; | |||
} | |||
| undefined; | |||
// type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
const StockInFormVer2: React.FC<Props> = ({ | |||
// qc, | |||
itemDetail, | |||
disabled, | |||
}) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
} = useTranslation("purchaseOrder"); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
register, | |||
formState: { errors, defaultValues, touchedFields }, | |||
watch, | |||
control, | |||
setValue, | |||
getValues, | |||
reset, | |||
resetField, | |||
setError, | |||
clearErrors, | |||
} = useFormContext<StockInInput>(); | |||
console.log(itemDetail); | |||
useEffect(() => { | |||
console.log("triggered"); | |||
// receiptDate default tdy | |||
setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||
setValue("status", "received"); | |||
}, [setValue]); | |||
useEffect(() => { | |||
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, clearErrors]); | |||
console.log(itemDetail) | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
{/* <Grid item xs={12}> | |||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||
{t("Stock In Detail")} | |||
</Typography> | |||
</Grid> */} | |||
<Grid | |||
container | |||
justifyContent="flex-start" | |||
alignItems="flex-start" | |||
spacing={2} | |||
sx={{ mt: 0.5 }} | |||
> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("dnNo")} | |||
fullWidth | |||
{...register("dnNo", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={true} | |||
// error={Boolean(errors.productLotNo)} | |||
// helperText={errors.productLotNo?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("itemName")} | |||
fullWidth | |||
{...register("itemName", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={true} | |||
// error={Boolean(errors.productLotNo)} | |||
// helperText={errors.productLotNo?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("PO No.")} | |||
fullWidth | |||
{...register("poCode", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={true} | |||
// error={Boolean(errors.productLotNo)} | |||
// helperText={errors.productLotNo?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<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"))} | |||
disabled={true} | |||
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={6}> | |||
<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} | |||
disabled={disabled} | |||
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={6}> | |||
<TextField | |||
label={t("productLotNo")} | |||
fullWidth | |||
{...register("productLotNo", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={disabled} | |||
error={Boolean(errors.productLotNo)} | |||
helperText={errors.productLotNo?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<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} | |||
disabled={disabled} | |||
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 item xs={6}> | |||
<TextField | |||
label={t("acceptedQty")} | |||
fullWidth | |||
{...register("acceptedQty", { | |||
required: "acceptedQty required!", | |||
})} | |||
disabled={disabled} | |||
error={Boolean(errors.acceptedQty)} | |||
helperText={errors.acceptedQty?.message} | |||
/> | |||
</Grid> | |||
{/* <Grid item xs={4}> | |||
<TextField | |||
label={t("acceptedWeight")} | |||
fullWidth | |||
// {...register("acceptedWeight", { | |||
// required: "acceptedWeight required!", | |||
// })} | |||
disabled={disabled} | |||
error={Boolean(errors.acceptedWeight)} | |||
helperText={errors.acceptedWeight?.message} | |||
/> | |||
</Grid> */} | |||
</Grid> | |||
<Grid | |||
container | |||
justifyContent="flex-start" | |||
alignItems="flex-start" | |||
spacing={2} | |||
sx={{ mt: 0.5 }} | |||
> | |||
{/* <Grid item xs={12}> | |||
<InputDataGrid<PurchaseQCInput, PurchaseQcResult, EntryError> | |||
apiRef={apiRef} | |||
checkboxSelection={false} | |||
_formKey={"qcCheck"} | |||
columns={columns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> */} | |||
</Grid> | |||
</Grid> | |||
); | |||
}; | |||
export default StockInFormVer2; |
@@ -0,0 +1,23 @@ | |||
const dummyQCData = [ | |||
{ | |||
id: 1, | |||
qcItem: "目測", | |||
isPassed: undefined, | |||
failedQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 2, | |||
qcItem: "目測2", | |||
isPassed: undefined, | |||
failedQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 3, | |||
qcItem: "目測3", | |||
isPassed: undefined, | |||
failedQty: undefined, | |||
remarks: undefined, | |||
}, | |||
] |
@@ -253,7 +253,7 @@ const RSOverview: React.FC<Props> = ({ type, defaultInputs }) => { | |||
// setFilterObj({}); | |||
// setTempSelectedValue({}); | |||
refetchData(defaultInputs, "reset"); | |||
}, []); | |||
}, [defaultInputs, refetchData]); | |||
const testRoughScheduleClick = useCallback(async () => { | |||
try { | |||
@@ -17,6 +17,9 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ | |||
"& .MuiDataGrid-columnSeparator": { | |||
color: theme.palette.primary.main, | |||
}, | |||
"& .MuiDataGrid-row:nth-of-type(even)": { | |||
backgroundColor: theme.palette.grey[200], // Light grey for even rows | |||
}, | |||
})); | |||
export default StyledDataGrid; |
@@ -82,6 +82,19 @@ | |||
"Po Code": "採購訂單編號", | |||
"No Warehouse": "沒有倉庫", | |||
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||
"receivedQty": "已來貨數量", | |||
"dnQty": "送貨單數量", | |||
"Accept submit": "接受來貨", | |||
"qc processing": "處理來貨及品檢", | |||
"putawayBtn": "上架", | |||
"dnNo": "送貨單編號", | |||
"dnDate": "送貨單日期", | |||
"submitStockIn": "更新來貨資料", | |||
"QC Info": "品檢資料", | |||
"Escalation History": "品檢資料", | |||
"Reject": "拒絕", | |||
"submit": "提交", | |||