@@ -13,7 +13,7 @@ import { UploadProvider } from "@/components/UploadProvider/UploadProvider"; | |||||
import SessionProviderWrapper from "@/components/SessionProviderWrapper/SessionProviderWrapper"; | import SessionProviderWrapper from "@/components/SessionProviderWrapper/SessionProviderWrapper"; | ||||
import QrCodeScannerProvider from "@/components/QrCodeScannerProvider/QrCodeScannerProvider"; | import QrCodeScannerProvider from "@/components/QrCodeScannerProvider/QrCodeScannerProvider"; | ||||
import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
import "src/app/global.css" | |||||
export default async function MainLayout({ | export default async function MainLayout({ | ||||
children, | children, | ||||
}: { | }: { | ||||
@@ -39,6 +39,7 @@ export interface InventoryLotLineResult { | |||||
uom: string; | uom: string; | ||||
qtyPerSmallestUnit: number; | qtyPerSmallestUnit: number; | ||||
baseUom: string; | baseUom: string; | ||||
stockInLineId: number | |||||
} | } | ||||
export interface InventoryLotLineItem { | export interface InventoryLotLineItem { | ||||
@@ -38,8 +38,10 @@ export interface PurchaseQcResult { | |||||
} | } | ||||
export interface StockInInput { | export interface StockInInput { | ||||
status: string; | status: string; | ||||
poCode: string; | |||||
productLotNo?: string; | productLotNo?: string; | ||||
dnNo?: string; | dnNo?: string; | ||||
itemName: string; | |||||
invoiceNo?: string; | invoiceNo?: string; | ||||
receiptDate: string; | receiptDate: string; | ||||
acceptedQty: number; | acceptedQty: number; | ||||
@@ -1,3 +1,7 @@ | |||||
@tailwind components; | @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", | type: "input-number", | ||||
style: { | style: { | ||||
textAlign: "right", | textAlign: "right", | ||||
// width: "100px", | |||||
}, | }, | ||||
renderCell: (row) => { | renderCell: (row) => { | ||||
if (typeof row.demandQty == "number") { | if (typeof row.demandQty == "number") { | ||||
@@ -174,6 +175,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
type: "read-only", | type: "read-only", | ||||
style: { | style: { | ||||
textAlign: "left", | textAlign: "left", | ||||
// width: "100px", | |||||
}, | }, | ||||
renderCell: (row) => { | renderCell: (row) => { | ||||
return row.uomName; | return row.uomName; | ||||
@@ -185,6 +187,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
type: "read-only", | type: "read-only", | ||||
style: { | style: { | ||||
textAlign: "right", | textAlign: "right", | ||||
// width: "100px", | |||||
}, | }, | ||||
renderCell: (row) => { | renderCell: (row) => { | ||||
return <ProdTimeColumn prodTimeInMinute={row.prodTimeInMinute} /> | return <ProdTimeColumn prodTimeInMinute={row.prodTimeInMinute} /> | ||||
@@ -196,6 +199,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit, type, onReleaseClick | |||||
type: "read-only", | type: "read-only", | ||||
style: { | style: { | ||||
textAlign: "right", | textAlign: "right", | ||||
// width: "100px", | |||||
}, | }, | ||||
// editable: true, | // editable: true, | ||||
}, | }, | ||||
@@ -84,9 +84,15 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
const [rows, setRows] = useState<PurchaseOrderLine[]>( | const [rows, setRows] = useState<PurchaseOrderLine[]>( | ||||
purchaseOrder.pol || [], | purchaseOrder.pol || [], | ||||
); | ); | ||||
const params = useSearchParams(); | |||||
const searchParams = useSearchParams(); | |||||
// const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status); | // 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 handleCompletePo = useCallback(async () => { | ||||
const checkRes = await checkPolAndCompletePo(purchaseOrder.id); | const checkRes = await checkPolAndCompletePo(purchaseOrder.id); | ||||
console.log(checkRes); | console.log(checkRes); | ||||
@@ -107,6 +113,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
function Row(props: { row: PurchaseOrderLine }) { | function Row(props: { row: PurchaseOrderLine }) { | ||||
const { row } = props; | const { row } = props; | ||||
const [firstReceiveQty, setFirstReceiveQty] = useState<number>() | |||||
const [secondReceiveQty, setSecondReceiveQty] = useState<number>() | |||||
const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
const [processedQty, setProcessedQty] = useState(row.processed); | const [processedQty, setProcessedQty] = useState(row.processed); | ||||
const [currStatus, setCurrStatus] = useState(row.status); | const [currStatus, setCurrStatus] = useState(row.status); | ||||
@@ -155,11 +163,30 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
<TableCell align="left">{row.price}</TableCell> | <TableCell align="left">{row.price}</TableCell> | ||||
{/* <TableCell align="left">{row.expiryDate}</TableCell> */} | {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | ||||
<TableCell align="left">{t(`${currStatus.toLowerCase()}`)}</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> | ||||
<TableRow> | <TableRow> | ||||
{/* <TableCell /> */} | {/* <TableCell /> */} | ||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={12}> | <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> | <Table> | ||||
<TableBody> | <TableBody> | ||||
<TableRow> | <TableRow> | ||||
@@ -260,7 +287,21 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
} | } | ||||
}, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); | }, [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 ( | return ( | ||||
<> | <> | ||||
<Stack | <Stack | ||||
@@ -277,18 +318,56 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
</Typography> | </Typography> | ||||
</Grid> | </Grid> | ||||
</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} | onClick={buttonData.onClick} | ||||
disabled={buttonData.disabled} | disabled={buttonData.disabled} | ||||
color={buttonData.buttonColor as ButtonProps["color"]} | color={buttonData.buttonColor as ButtonProps["color"]} | ||||
startIcon={buttonData.buttonIcon} | startIcon={buttonData.buttonIcon} | ||||
> | > | ||||
{buttonData.buttonText} | {buttonData.buttonText} | ||||
</Button> | |||||
</Button> */} | |||||
</Grid> | </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 container xs={12} justifyContent="space-between"> | ||||
<Grid item xs={8}> | <Grid item xs={8}> | ||||
<Tabs | <Tabs | ||||
@@ -296,7 +375,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
onChange={handleTabChange} | onChange={handleTabChange} | ||||
variant="scrollable" | variant="scrollable" | ||||
> | > | ||||
<Tab label={t("General")} iconPosition="end" /> | |||||
{/* <Tab label={t("General")} iconPosition="end" /> */} | |||||
{/* <Tab label={t("Bind Storage")} iconPosition="end" /> */} | {/* <Tab label={t("Bind Storage")} iconPosition="end" /> */} | ||||
</Tabs> | </Tabs> | ||||
</Grid> | </Grid> | ||||
@@ -315,7 +394,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
onClose={onCloseScanner} | onClose={onCloseScanner} | ||||
warehouse={warehouse} | warehouse={warehouse} | ||||
/> | /> | ||||
<Button onClick={onOpenScanner}>{t("bind")}</Button> | |||||
{/* <Button onClick={onOpenScanner}>{t("Accept submit")}</Button> */} | |||||
{/* <Button onClick={onOpenScanner}>{t("bind")}</Button> */} | |||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
{/* tab 1 */} | {/* tab 1 */} | ||||
@@ -336,6 +416,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
<TableCell align="left">{t("price")}</TableCell> | <TableCell align="left">{t("price")}</TableCell> | ||||
{/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | ||||
<TableCell align="left">{t("status")}</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> */} | {/* <TableCell align="left">{"add icon button"}</TableCell> */} | ||||
</TableRow> | </TableRow> | ||||
</TableHead> | </TableHead> | ||||
@@ -57,6 +57,7 @@ import { fetchQcResult } from "@/app/api/qc/actions"; | |||||
import PoQcStockInModal from "./PoQcStockInModal"; | import PoQcStockInModal from "./PoQcStockInModal"; | ||||
import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | ||||
import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
import PoQcStockInModalVer2 from "./QcStockInModalVer2"; | |||||
interface ResultWithId { | interface ResultWithId { | ||||
id: number; | id: number; | ||||
@@ -255,6 +256,40 @@ function PoInputGrid({ | |||||
}, | }, | ||||
[fetchQcDefaultValue, openQcModal], | [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( | const handleEscalation = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
// setBtnIsLoading(true); | // setBtnIsLoading(true); | ||||
@@ -373,20 +408,38 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "itemNo", | field: "itemNo", | ||||
headerName: t("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, | // flex: 0.4, | ||||
}, | }, | ||||
{ | { | ||||
field: "itemName", | field: "itemName", | ||||
headerName: t("itemName"), | headerName: t("itemName"), | ||||
width: 120, | |||||
width: 100, | |||||
// flex: 0.6, | // flex: 0.6, | ||||
}, | }, | ||||
{ | { | ||||
field: "acceptedQty", | field: "acceptedQty", | ||||
headerName: t("acceptedQty"), | headerName: t("acceptedQty"), | ||||
// flex: 0.5, | // flex: 0.5, | ||||
width: 120, | |||||
width: 100, | |||||
type: "number", | type: "number", | ||||
// editable: true, | // editable: true, | ||||
// replace with tooltip + content | // replace with tooltip + content | ||||
@@ -417,7 +470,7 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "status", | field: "status", | ||||
headerName: t("status"), | headerName: t("status"), | ||||
width: 120, | |||||
width: 70, | |||||
// flex: 0.5, | // flex: 0.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return t(`${params.row.status}`); | return t(`${params.row.status}`); | ||||
@@ -426,12 +479,13 @@ function PoInputGrid({ | |||||
{ | { | ||||
field: "actions", | field: "actions", | ||||
type: "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", | // headerName: "start | qc | escalation | stock in | putaway | delete", | ||||
width: 300, | width: 300, | ||||
// flex: 1.5, | |||||
// flex: 2, | |||||
cellClassName: "actions", | cellClassName: "actions", | ||||
getActions: (params) => { | getActions: (params) => { | ||||
// console.log(params.row.status); | // console.log(params.row.status); | ||||
@@ -440,130 +494,158 @@ function PoInputGrid({ | |||||
// console.log(session?.user?.abilities?.includes("APPROVAL")); | // console.log(session?.user?.abilities?.includes("APPROVAL")); | ||||
return [ | return [ | ||||
<GridActionsCellItem | <GridActionsCellItem | ||||
icon={<PlayArrowIcon />} | |||||
icon={<Button variant="contained">{t("qc processing")}</Button>} | |||||
label="start" | label="start" | ||||
sx={{ | sx={{ | ||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // 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 | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleEscalation(params.row.id, params)} | |||||
onClick={handleNewQC(params.row.id, params)} | |||||
color="inherit" | color="inherit" | ||||
key="edit" | key="edit" | ||||
/>, | />, | ||||
<GridActionsCellItem | <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={{ | sx={{ | ||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={ | |||||
stockInLineStatusMap[status] === 9 || | |||||
stockInLineStatusMap[status] < 7 | |||||
} | |||||
// disabled={!(stockInLineStatusMap[status] === 0)} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handlePutAway(params.row.id, params)} | |||||
onClick={handleStart(params.row.id, params)} | |||||
color="inherit" | color="inherit" | ||||
key="edit" | key="edit" | ||||
/>, | />, | ||||
// <GridActionsCellItem | // <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" | // label="putaway" | ||||
// sx={{ | // sx={{ | ||||
// color: "primary.main", | // color: "primary.main", | ||||
// // marginRight: 1, | // // marginRight: 1, | ||||
// }} | // }} | ||||
// disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||||
// disabled={ | |||||
// stockInLineStatusMap[status] === 9 || | |||||
// stockInLineStatusMap[status] < 7 | |||||
// } | |||||
// // set _isNew to false after posting | // // set _isNew to false after posting | ||||
// // or check status | // // or check status | ||||
// onClick={handleQrCode(params.row.id, params)} | |||||
// onClick={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" | // color="inherit" | ||||
// key="edit" | // 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]); | }, [currQty, getRowId, itemDetail]); | ||||
const validation = useCallback( | const validation = useCallback( | ||||
( | ( | ||||
newRow: GridRowModel<StockInLineRow>, | newRow: GridRowModel<StockInLineRow>, | ||||
@@ -654,20 +737,22 @@ function PoInputGrid({ | |||||
); | ); | ||||
const footer = ( | 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 ( | return ( | ||||
<> | <> | ||||
<StyledDataGrid | <StyledDataGrid | ||||
@@ -715,6 +800,21 @@ function PoInputGrid({ | |||||
footer: { child: footer }, | footer: { child: footer }, | ||||
}} | }} | ||||
/> | /> | ||||
{modalInfo !== undefined && ( | |||||
<> | |||||
<PoQcStockInModalVer2 | |||||
// setRows={setRows} | |||||
setEntries={setEntries} | |||||
setStockInLine={setStockInLine} | |||||
setItemDetail={setModalInfo} | |||||
qc={qc} | |||||
open={newOpen} | |||||
onClose={closeNewModal} | |||||
itemDetail={modalInfo} | |||||
/> | |||||
</> | |||||
) | |||||
} | |||||
{modalInfo !== undefined && ( | {modalInfo !== undefined && ( | ||||
<> | <> | ||||
<PoQcStockInModal | <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({}); | // setFilterObj({}); | ||||
// setTempSelectedValue({}); | // setTempSelectedValue({}); | ||||
refetchData(defaultInputs, "reset"); | refetchData(defaultInputs, "reset"); | ||||
}, []); | |||||
}, [defaultInputs, refetchData]); | |||||
const testRoughScheduleClick = useCallback(async () => { | const testRoughScheduleClick = useCallback(async () => { | ||||
try { | try { | ||||
@@ -17,6 +17,9 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ | |||||
"& .MuiDataGrid-columnSeparator": { | "& .MuiDataGrid-columnSeparator": { | ||||
color: theme.palette.primary.main, | color: theme.palette.primary.main, | ||||
}, | }, | ||||
"& .MuiDataGrid-row:nth-of-type(even)": { | |||||
backgroundColor: theme.palette.grey[200], // Light grey for even rows | |||||
}, | |||||
})); | })); | ||||
export default StyledDataGrid; | export default StyledDataGrid; |
@@ -82,6 +82,19 @@ | |||||
"Po Code": "採購訂單編號", | "Po Code": "採購訂單編號", | ||||
"No Warehouse": "沒有倉庫", | "No Warehouse": "沒有倉庫", | ||||
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | ||||
"receivedQty": "已來貨數量", | |||||
"dnQty": "送貨單數量", | |||||
"Accept submit": "接受來貨", | |||||
"qc processing": "處理來貨及品檢", | |||||
"putawayBtn": "上架", | |||||
"dnNo": "送貨單編號", | |||||
"dnDate": "送貨單日期", | |||||
"submitStockIn": "更新來貨資料", | |||||
"QC Info": "品檢資料", | |||||
"Escalation History": "品檢資料", | |||||
"Reject": "拒絕", | "Reject": "拒絕", | ||||
"submit": "提交", | "submit": "提交", | ||||