@@ -1,71 +1,395 @@ | |||||
"use client" | |||||
import { MutableRefObject } from "react"; | |||||
import StyledDataGrid from "../StyledDataGrid" | |||||
import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||||
import { GridColDef } from "@mui/x-data-grid"; | |||||
"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 { useTranslation } from "react-i18next"; | ||||
import { dummyQCData, QcData } from "./dummyQcTemplate"; | |||||
import { Checkbox } from "@mui/material"; | |||||
import { | |||||
GridApiCommunity, | |||||
GridSlotsComponentsProps, | |||||
} from "@mui/x-data-grid/internals"; | |||||
import { dummyQCData } from "./dummyQcTemplate"; | |||||
// 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 Props { | |||||
// apiRef: MutableRefObject<GridApiCommunity>; | |||||
}; | |||||
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; | |||||
} | |||||
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 InputDataGrid<T, V, E>({ | |||||
apiRef, | |||||
// checkboxSelection = false, | |||||
_formKey, | |||||
columns, | |||||
validateRow, | |||||
}: Props<T, V, E>) { | |||||
const { | |||||
t, | |||||
// i18n: { language }, | |||||
} = useTranslation("purchaseOrder"); | |||||
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 formValue = getValues(formKey) | |||||
const list: TableRow<V, E>[] = !formValue || formValue.length == 0 ? dummyQCData : getValues(formKey); | |||||
console.log(list) | |||||
const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | |||||
// const list: TableRow<V, E>[] = getValues(formKey); | |||||
console.log(list) | |||||
return list && list.length > 0 ? list : []; | |||||
}); | |||||
console.log(rows) | |||||
// 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; | |||||
}); | |||||
useEffect(() => { | |||||
for (let i = 0; i < rows.length; i++) { | |||||
const currRow = rows[i] | |||||
setRowModesModel((prevRowModesModel) => ({ | |||||
...prevRowModesModel, | |||||
[currRow.id as number]: { mode: GridRowModes.View }, | |||||
})); | |||||
} | |||||
}, [rows]) | |||||
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 QcDataGrid: React.FC<Props> = ({ | |||||
// apiRef | |||||
}) => { | |||||
const { t } = useTranslation("purchaseOrder"); | |||||
const columns: GridColDef[] = [ | |||||
{ | |||||
field: "qcItem", | |||||
headerName: t("qcItem"), | |||||
flex: 1, | |||||
}, | |||||
{ | |||||
field: "isPassed", | |||||
headerName: t("passed"), | |||||
flex: 1, | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={params.value} | |||||
// onChange={() => handleCheckboxChange(params.id)} | |||||
/> | |||||
), | |||||
}, | |||||
{ | |||||
field: "isFailed", | |||||
headerName: t("failed"), | |||||
flex: 1, | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={params.value} | |||||
// onChange={() => handleCheckboxChange(params.id)} | |||||
/> | |||||
), | |||||
}, | |||||
{ | |||||
field: "failedQty", | |||||
headerName: t("failedQty"), | |||||
flex: 1, | |||||
editable: true, | |||||
}, | |||||
{ | |||||
field: "remarks", | |||||
headerName: t("remarks"), | |||||
flex: 1, | |||||
editable: true, | |||||
}, | |||||
] | |||||
return ( | |||||
<StyledDataGrid | |||||
// apiRef={apiRef} | |||||
autoHeight | |||||
editMode="row" | |||||
rows={dummyQCData} | |||||
columns={columns} | |||||
/> | |||||
) | |||||
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>} | |||||
rowSelectionModel={rowSelectionModel} | |||||
apiRef={apiRef} | |||||
rows={rows} | |||||
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={{ | |||||
// footer: FooterToolbar, | |||||
noRowsOverlay: NoRowsOverlay, | |||||
}} | |||||
// slotProps={{ | |||||
// footer: { child: footer }, | |||||
// } | |||||
// } | |||||
/> | |||||
); | |||||
} | } | ||||
export default QcDataGrid | |||||
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 InputDataGrid; |
@@ -5,6 +5,7 @@ import { | |||||
Box, | Box, | ||||
Card, | Card, | ||||
CardContent, | CardContent, | ||||
Checkbox, | |||||
Grid, | Grid, | ||||
Stack, | Stack, | ||||
Tab, | Tab, | ||||
@@ -17,7 +18,7 @@ import { | |||||
import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { | import { | ||||
GridColDef, | GridColDef, | ||||
GridRowIdGetter, | GridRowIdGetter, | ||||
@@ -43,22 +44,25 @@ import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||||
import EscalationComponent from "./EscalationComponent"; | import EscalationComponent from "./EscalationComponent"; | ||||
import QcDataGrid from "./QCDatagrid"; | import QcDataGrid from "./QCDatagrid"; | ||||
import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
import { dummyEscalationHistory } from "./dummyQcTemplate"; | |||||
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||||
import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
disabled: boolean; | disabled: boolean; | ||||
qcItems: QcData[] | |||||
setQcItems: Dispatch<SetStateAction<QcData[]>> | |||||
} | } | ||||
type EntryError = | type EntryError = | ||||
| { | | { | ||||
[field in keyof PurchaseQcResult]?: string; | |||||
[field in keyof QcData]?: string; | |||||
} | } | ||||
| undefined; | | undefined; | ||||
type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||||
type QcRow = TableRow<Partial<QcData>, EntryError>; | |||||
// fetchQcItemCheck | // fetchQcItemCheck | ||||
const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcItems }) => { | |||||
const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const { | const { | ||||
@@ -76,6 +80,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>() | const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>() | ||||
const [qcResult, setQcResult] = useState(dummyEscalationHistory) | |||||
// const [qcItems, setQcItems] = useState(dummyQCData) | |||||
const column = useMemo<GridColDef[]>( | const column = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
@@ -137,18 +143,18 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
// flex: 1, | // flex: 1, | ||||
// editable: !disabled, | // editable: !disabled, | ||||
// valueFormatter(params) { | // valueFormatter(params) { | ||||
// const row = params.id ? params.api.getRow<PoQcRow>(params.id) : null; | |||||
// const row = params.id ? params.api.getRow<QcRow>(params.id) : null; | |||||
// if (!row) { | // if (!row) { | ||||
// return null; | // return null; | ||||
// } | // } | ||||
// const Qc = qc.find((q) => q.id === row.qcItemId); | // const Qc = qc.find((q) => q.id === row.qcItemId); | ||||
// return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | // return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | ||||
// }, | // }, | ||||
// renderCell(params: GridRenderCellParams<PoQcRow, number>) { | |||||
// renderCell(params: GridRenderCellParams<QcRow, number>) { | |||||
// console.log(params.value); | // console.log(params.value); | ||||
// return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | // return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | ||||
// }, | // }, | ||||
// renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||||
// renderEditCell(params: GridRenderEditCellParams<QcRow, number>) { | |||||
// const errorMessage = | // const errorMessage = | ||||
// params.row._error?.[params.field as keyof PurchaseQcResult]; | // params.row._error?.[params.field as keyof PurchaseQcResult]; | ||||
// console.log(errorMessage); | // console.log(errorMessage); | ||||
@@ -185,7 +191,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
// flex: 1, | // flex: 1, | ||||
// editable: !disabled, | // editable: !disabled, | ||||
// type: "number", | // type: "number", | ||||
// renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||||
// renderEditCell(params: GridRenderEditCellParams<QcRow>) { | |||||
// // const recordQty = params.row.qty | // // const recordQty = params.row.qty | ||||
// // if (recordQty !== undefined) { | // // if (recordQty !== undefined) { | ||||
// // setUnrecordQty((prev) => prev - recordQty) | // // setUnrecordQty((prev) => prev - recordQty) | ||||
@@ -217,23 +223,124 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
); | ); | ||||
/// validate datagrid | /// validate datagrid | ||||
const validation = useCallback( | const validation = useCallback( | ||||
(newRow: GridRowModel<PoQcRow>): EntryError => { | |||||
(newRow: GridRowModel<QcRow>): EntryError => { | |||||
const error: 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"); | |||||
} | |||||
// const { qcItemId, failQty } = newRow; | |||||
return Object.keys(error).length > 0 ? error : undefined; | return Object.keys(error).length > 0 ? error : undefined; | ||||
}, | }, | ||||
[], | [], | ||||
); | ); | ||||
function BooleanEditCell(params: GridRenderEditCellParams) { | |||||
const apiRef = useGridApiContext(); | |||||
const { id, field, value } = params; | |||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||||
apiRef.current.setEditCellValue({ id, field, value: e.target.checked }); | |||||
apiRef.current.stopCellEditMode({ id, field }); // commit immediately | |||||
}; | |||||
return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />; | |||||
} | |||||
const qcColumns: GridColDef[] = [ | |||||
{ | |||||
field: "qcItem", | |||||
headerName: t("qcItem"), | |||||
flex: 1, | |||||
}, | |||||
{ | |||||
field: 'isPassed', | |||||
headerName: t("passed"), | |||||
flex: 1, | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={!!params.value} | |||||
onClick={(e) => e.stopPropagation()} // avoid row selection | |||||
onMouseDown={(e) => e.stopPropagation()} // extra guard | |||||
onChange={(e) => { | |||||
const checked = e.target.checked; | |||||
setQcItems((prev) => | |||||
prev.map((r) => (r.id === params.id ? { ...r, isPassed: checked } : r)) | |||||
); | |||||
}} | |||||
size="small" | |||||
/> | |||||
), | |||||
}, | |||||
{ | |||||
field: "isFailed", | |||||
headerName: t("failed"), | |||||
flex: 1, | |||||
editable: true, | |||||
type: "boolean", | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={!!params.value} | |||||
onClick={(e) => e.stopPropagation()} // avoid row selection | |||||
onMouseDown={(e) => e.stopPropagation()} // extra guard | |||||
onChange={(e) => { | |||||
const checked = e.target.checked; | |||||
setQcItems((prev) => | |||||
prev.map((r) => (r.id === params.id ? { ...r, isFailed: checked } : r)) | |||||
); | |||||
}} | |||||
size="small" | |||||
/> | |||||
), | |||||
}, | |||||
{ | |||||
field: "failedQty", | |||||
headerName: t("failedQty"), | |||||
flex: 1, | |||||
// editable: true, | |||||
renderCell: (params) => ( | |||||
<TextField | |||||
type="number" | |||||
size="small" | |||||
value={params.value ?? ''} | |||||
onChange={(e) => { | |||||
const v = e.target.value; | |||||
const next = v === '' ? undefined : Number(v); | |||||
if (Number.isNaN(next)) return; | |||||
setQcItems((prev) => | |||||
prev.map((r) => (r.id === params.id ? { ...r, failedQty: next } : r)) | |||||
); | |||||
}} | |||||
onClick={(e) => e.stopPropagation()} | |||||
onMouseDown={(e) => e.stopPropagation()} | |||||
onKeyDown={(e) => e.stopPropagation()} | |||||
inputProps={{ min: 0 }} | |||||
sx={{ width: '100%' }} | |||||
/> | |||||
), | |||||
}, | |||||
{ | |||||
field: "remarks", | |||||
headerName: t("remarks"), | |||||
flex: 1, | |||||
renderCell: (params) => ( | |||||
<TextField | |||||
size="small" | |||||
value={params.value ?? ''} | |||||
onChange={(e) => { | |||||
const remarks = e.target.value; | |||||
// const next = v === '' ? undefined : Number(v); | |||||
// if (Number.isNaN(next)) return; | |||||
setQcItems((prev) => | |||||
prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) | |||||
); | |||||
}} | |||||
onClick={(e) => e.stopPropagation()} | |||||
onMouseDown={(e) => e.stopPropagation()} | |||||
onKeyDown={(e) => e.stopPropagation()} | |||||
inputProps={{ min: 0 }} | |||||
sx={{ width: '100%' }} | |||||
/> | |||||
), | |||||
}, | |||||
] | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
const status = "receiving"; | const status = "receiving"; | ||||
@@ -268,10 +375,21 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
{tabIndex == 0 && ( | {tabIndex == 0 && ( | ||||
<> | <> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<QcDataGrid/> | |||||
{/* <QcDataGrid<ModalFormInput, QcData, EntryError> | |||||
apiRef={apiRef} | |||||
columns={qcColumns} | |||||
_formKey="qcResult" | |||||
validateRow={validation} | |||||
/> */} | |||||
<StyledDataGrid | |||||
columns={qcColumns} | |||||
rows={qcItems} | |||||
autoHeight | |||||
/> | |||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | <Grid item xs={4}> | ||||
<TextField | <TextField | ||||
type="number" | |||||
label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
fullWidth | fullWidth | ||||
/> | /> | ||||
@@ -296,7 +414,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||||
</Grid> | </Grid> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<StyledDataGrid | <StyledDataGrid | ||||
rows={dummyEscalationHistory} | |||||
rows={qcResult} | |||||
columns={columns} | columns={columns} | ||||
onRowSelectionModelChange={(newRowSelectionModel) => { | onRowSelectionModelChange={(newRowSelectionModel) => { | ||||
setRowSelectionModel(newRowSelectionModel); | setRowSelectionModel(newRowSelectionModel); | ||||
@@ -1,9 +1,16 @@ | |||||
"use client"; | "use client"; | ||||
import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
import { ModalFormInput, PurchaseQcResult } from "@/app/api/po/actions"; | import { ModalFormInput, PurchaseQcResult } from "@/app/api/po/actions"; | ||||
import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
import { Box, Button, Grid, Modal, ModalProps, Stack, Typography } from "@mui/material"; | |||||
import { | |||||
Box, | |||||
Button, | |||||
Grid, | |||||
Modal, | |||||
ModalProps, | |||||
Stack, | |||||
Typography, | |||||
} from "@mui/material"; | |||||
import { Dispatch, SetStateAction, useCallback, useState } from "react"; | import { Dispatch, SetStateAction, useCallback, useState } from "react"; | ||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
@@ -12,8 +19,8 @@ import StockInForm from "./StockInForm"; | |||||
import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
import QcFormVer2 from "./QcFormVer2"; | import QcFormVer2 from "./QcFormVer2"; | ||||
import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
import { dummyPutawayLine } from "./dummyQcTemplate"; | |||||
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | |||||
import { useGridApiRef } from "@mui/x-data-grid"; | |||||
const style = { | const style = { | ||||
position: "absolute", | position: "absolute", | ||||
top: "50%", | top: "50%", | ||||
@@ -25,9 +32,8 @@ const style = { | |||||
pb: 10, | pb: 10, | ||||
display: "block", | display: "block", | ||||
width: { xs: "60%", sm: "60%", md: "60%" }, | width: { xs: "60%", sm: "60%", md: "60%" }, | ||||
// height: { xs: "60%", sm: "60%", md: "60%" }, | |||||
// height: { xs: "60%", sm: "60%", md: "60%" }, | |||||
}; | }; | ||||
interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | ||||
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | ||||
@@ -43,12 +49,10 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||||
>; | >; | ||||
qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
warehouse?: any[]; | warehouse?: any[]; | ||||
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||||
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||||
} | } | ||||
interface Props extends CommonProps{ | |||||
interface Props extends CommonProps { | |||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | ||||
} | } | ||||
const PoQcStockInModalVer2: React.FC<Props> = ({ | const PoQcStockInModalVer2: React.FC<Props> = ({ | ||||
// type, | // type, | ||||
@@ -62,165 +66,243 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
qc, | qc, | ||||
warehouse, | warehouse, | ||||
}) => { | }) => { | ||||
console.log(warehouse) | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation("purchaseOrder"); | |||||
console.log(warehouse); | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation("purchaseOrder"); | |||||
const [qcItems, setQcItems] = useState(dummyQCData) | |||||
const formProps = useForm<ModalFormInput>({ | const formProps = useForm<ModalFormInput>({ | ||||
defaultValues: { | defaultValues: { | ||||
...itemDetail, | ...itemDetail, | ||||
putawayLine: dummyPutawayLine | |||||
putawayLine: dummyPutawayLine, | |||||
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | ||||
// warehouseId: itemDetail.defaultWarehouseId || 0 | // warehouseId: itemDetail.defaultWarehouseId || 0 | ||||
}, | }, | ||||
}); | }); | ||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
(...args) => { | |||||
onClose?.(...args); | |||||
// reset(); | |||||
}, | |||||
[onClose], | |||||
); | |||||
const [openPutaway, setOpenPutaway] = useState(false) | |||||
(...args) => { | |||||
onClose?.(...args); | |||||
// reset(); | |||||
}, | |||||
[onClose], | |||||
); | |||||
const [openPutaway, setOpenPutaway] = useState(false); | |||||
const onOpenPutaway = useCallback(() => { | const onOpenPutaway = useCallback(() => { | ||||
setOpenPutaway(true); | setOpenPutaway(true); | ||||
}, []); | }, []); | ||||
const onClosePutaway = useCallback(() => { | const onClosePutaway = useCallback(() => { | ||||
setOpenPutaway(false); | setOpenPutaway(false); | ||||
}, []); | }, []); | ||||
const [submissionType, setSubmissionType] = useState<"stockIn" | "qc" | "escalate" | undefined>(undefined) | |||||
const onSubmit = useCallback<SubmitHandler<ModalFormInput>>( | |||||
// Stock In submission handler | |||||
const onSubmitStockIn = useCallback<SubmitHandler<ModalFormInput>>( | |||||
async (data, event) => { | async (data, event) => { | ||||
console.log(event!.nativeEvent) | |||||
// closeHandler({}, "backdropClick"); | |||||
// for now go to putaway form | |||||
onOpenPutaway() | |||||
// divide 3 section for this submition | |||||
// switch (submissionType) { | |||||
// submit stock in data | |||||
// submit qc data | |||||
// submit putaway | |||||
// } | |||||
console.log("Stock In Submission:", event!.nativeEvent); | |||||
// Extract only stock-in related fields | |||||
const stockInData = { | |||||
// quantity: data.quantity, | |||||
// receiptDate: data.receiptDate, | |||||
// batchNumber: data.batchNumber, | |||||
// expiryDate: data.expiryDate, | |||||
// warehouseId: data.warehouseId, | |||||
// location: data.location, | |||||
// unitCost: data.unitCost, | |||||
data: data, | |||||
// Add other stock-in specific fields from your form | |||||
}; | |||||
console.log("Stock In Data:", stockInData); | |||||
// Handle stock-in submission logic here | |||||
// e.g., call API, update state, etc. | |||||
}, | |||||
[], | |||||
); | |||||
// QC submission handler | |||||
const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | |||||
async (data, event) => { | |||||
console.log("QC Submission:", event!.nativeEvent); | |||||
// Extract only QC related fields | |||||
const qcData = { | |||||
// qcStatus: data.qcStatus, | |||||
// qcComments: data.qcComments, | |||||
// qcResult: data.qcResult, | |||||
// approvedBy: data.approvedBy, | |||||
// qualityGrade: data.qualityGrade, | |||||
// defectNotes: data.defectNotes, | |||||
data: data, | |||||
// Add other QC specific fields from your form | |||||
}; | |||||
console.log(qcItems) | |||||
console.log("QC Data:", qcData); | |||||
// Handle QC submission logic here | |||||
// After QC approval, open putaway form | |||||
// onOpenPutaway(); | |||||
}, | |||||
[onOpenPutaway, qcItems], | |||||
); | |||||
// Email supplier handler | |||||
const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>( | |||||
async (data, event) => { | |||||
console.log("Email Supplier Submission:", event!.nativeEvent); | |||||
// Extract only email supplier related fields | |||||
const emailData = { | |||||
// supplierEmail: data.supplierEmail, | |||||
// issueDescription: data.issueDescription, | |||||
// qcComments: data.qcComments, | |||||
// defectNotes: data.defectNotes, | |||||
// attachments: data.attachments, | |||||
// escalationReason: data.escalationReason, | |||||
data: data, | |||||
}, [submissionType]) | |||||
// Add other email-specific fields | |||||
}; | |||||
console.log("Email Supplier Data:", emailData); | |||||
// Handle email supplier logic here | |||||
// e.g., send email to supplier, log escalation, etc. | |||||
}, | |||||
[], | |||||
); | |||||
// Putaway submission handler | |||||
const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>( | |||||
async (data, event) => { | |||||
console.log("Putaway Submission:", event!.nativeEvent); | |||||
// Extract only putaway related fields | |||||
const putawayData = { | |||||
// putawayLine: data.putawayLine, | |||||
// putawayLocation: data.putawayLocation, | |||||
// binLocation: data.binLocation, | |||||
// putawayQuantity: data.putawayQuantity, | |||||
// putawayNotes: data.putawayNotes, | |||||
data: data, | |||||
return ( | |||||
<> | |||||
{/* {itemDetail !== undefined && ( | |||||
<PutawayForm | |||||
itemDetail={itemDetail} | |||||
warehouse={warehouse!} | |||||
disabled={false} | |||||
/> | |||||
)} */} | |||||
<FormProvider {...formProps}> | |||||
<Modal open={open} onClose={closeHandler}> | |||||
<Box | |||||
sx={{ | |||||
...style, | |||||
padding: 2, // Add padding to the Box | |||||
maxHeight: '90vh', // Limit the height of the modal | |||||
overflowY: 'auto', // Enable scrolling if content overflows | |||||
marginLeft: 3, | |||||
marginRight: 3, | |||||
}} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit)} | |||||
> | |||||
{openPutaway ? ( | |||||
<> | |||||
<PutawayForm | |||||
itemDetail={itemDetail} | |||||
warehouse={warehouse!} | |||||
disabled={false} | |||||
/> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
id="qc" | |||||
type="button" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
> | |||||
{t("print")} | |||||
</Button> | |||||
<Button | |||||
id="qc" | |||||
type="submit" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
> | |||||
{t("confirm putaway")} | |||||
</Button> | |||||
</Stack> | |||||
</> | |||||
// Add other putaway specific fields | |||||
}; | |||||
console.log("Putaway Data:", putawayData); | |||||
// Handle putaway submission logic here | |||||
// Close modal after successful putaway | |||||
closeHandler({}, "backdropClick"); | |||||
}, | |||||
[closeHandler], | |||||
); | |||||
// Print handler | |||||
const onPrint = useCallback(() => { | |||||
console.log("Print putaway documents"); | |||||
// Handle print logic here | |||||
window.print(); | |||||
}, []); | |||||
return ( | |||||
<> | |||||
<FormProvider {...formProps}> | |||||
<Modal open={open} onClose={closeHandler}> | |||||
<Box | |||||
sx={{ | |||||
...style, | |||||
padding: 2, | |||||
maxHeight: "90vh", | |||||
overflowY: "auto", | |||||
marginLeft: 3, | |||||
marginRight: 3, | |||||
}} | |||||
> | |||||
{openPutaway ? ( | |||||
<Box | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmitPutaway)} | |||||
> | |||||
<PutawayForm | |||||
itemDetail={itemDetail} | |||||
warehouse={warehouse!} | |||||
disabled={false} | |||||
/> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
id="printButton" | |||||
type="button" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
onClick={onPrint} | |||||
> | |||||
{t("print")} | |||||
</Button> | |||||
<Button | |||||
id="putawaySubmit" | |||||
type="submit" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
> | |||||
{t("confirm putaway")} | |||||
</Button> | |||||
</Stack> | |||||
</Box> | |||||
) : ( | ) : ( | ||||
<> | |||||
<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> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
id="qc" | |||||
type="button" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
> | |||||
{t("email supplier")} | |||||
</Button> | |||||
<Button | |||||
id="qc" | |||||
type="submit" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
> | |||||
{t("confirm putaway")} | |||||
</Button> | |||||
</Stack> | |||||
</> | |||||
) | |||||
} | |||||
</Box> | |||||
</Modal> | |||||
</FormProvider> | |||||
</> | |||||
) | |||||
} | |||||
export default PoQcStockInModalVer2 | |||||
<> | |||||
<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="stockInSubmit" | |||||
type="button" | |||||
variant="contained" | |||||
color="primary" | |||||
onClick={formProps.handleSubmit(onSubmitStockIn)} | |||||
> | |||||
{t("submitStockIn")} | |||||
</Button> | |||||
</Stack> | |||||
<Grid | |||||
container | |||||
justifyContent="flex-start" | |||||
alignItems="flex-start" | |||||
> | |||||
<QcFormVer2 | |||||
qc={qc!} | |||||
itemDetail={itemDetail} | |||||
disabled={false} | |||||
qcItems={qcItems} | |||||
setQcItems={setQcItems} | |||||
/> | |||||
</Grid> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
id="emailSupplier" | |||||
type="button" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | |||||
> | |||||
{t("email supplier")} | |||||
</Button> | |||||
<Button | |||||
id="qcSubmit" | |||||
type="button" | |||||
variant="contained" | |||||
color="secondary" | |||||
sx={{ mt: 1 }} | |||||
onClick={formProps.handleSubmit(onSubmitQc)} | |||||
> | |||||
{t("confirm putaway")} | |||||
</Button> | |||||
</Stack> | |||||
</> | |||||
)} | |||||
</Box> | |||||
</Modal> | |||||
</FormProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default PoQcStockInModalVer2; |
@@ -82,7 +82,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
// receiptDate default tdy | // receiptDate default tdy | ||||
setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | ||||
setValue("status", "received"); | setValue("status", "received"); | ||||
}, []); | |||||
}, [setValue]); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log(errors); | console.log(errors); | ||||
@@ -97,7 +97,7 @@ const StockInForm: React.FC<Props> = ({ | |||||
if (expiryDate) clearErrors(); | if (expiryDate) clearErrors(); | ||||
if (productionDate) clearErrors(); | if (productionDate) clearErrors(); | ||||
}, [productionDate, expiryDate, clearErrors]); | }, [productionDate, expiryDate, clearErrors]); | ||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||