@@ -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 { 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, | |||
Card, | |||
CardContent, | |||
Checkbox, | |||
Grid, | |||
Stack, | |||
Tab, | |||
@@ -17,7 +18,7 @@ import { | |||
import { useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
@@ -43,22 +44,25 @@ import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
import EscalationComponent from "./EscalationComponent"; | |||
import QcDataGrid from "./QCDatagrid"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import { dummyEscalationHistory } from "./dummyQcTemplate"; | |||
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
qc: QcItemWithChecks[]; | |||
disabled: boolean; | |||
qcItems: QcData[] | |||
setQcItems: Dispatch<SetStateAction<QcData[]>> | |||
} | |||
type EntryError = | |||
| { | |||
[field in keyof PurchaseQcResult]?: string; | |||
[field in keyof QcData]?: string; | |||
} | |||
| undefined; | |||
type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
type QcRow = TableRow<Partial<QcData>, EntryError>; | |||
// fetchQcItemCheck | |||
const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcItems }) => { | |||
const { t } = useTranslation("purchaseOrder"); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
@@ -76,6 +80,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>() | |||
const [qcResult, setQcResult] = useState(dummyEscalationHistory) | |||
// const [qcItems, setQcItems] = useState(dummyQCData) | |||
const column = useMemo<GridColDef[]>( | |||
() => [ | |||
@@ -137,18 +143,18 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
// flex: 1, | |||
// editable: !disabled, | |||
// 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) { | |||
// 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>) { | |||
// renderCell(params: GridRenderCellParams<QcRow, number>) { | |||
// console.log(params.value); | |||
// return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||
// }, | |||
// renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||
// renderEditCell(params: GridRenderEditCellParams<QcRow, number>) { | |||
// const errorMessage = | |||
// params.row._error?.[params.field as keyof PurchaseQcResult]; | |||
// console.log(errorMessage); | |||
@@ -185,7 +191,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
// flex: 1, | |||
// editable: !disabled, | |||
// type: "number", | |||
// renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||
// renderEditCell(params: GridRenderEditCellParams<QcRow>) { | |||
// // const recordQty = params.row.qty | |||
// // if (recordQty !== undefined) { | |||
// // setUnrecordQty((prev) => prev - recordQty) | |||
@@ -217,23 +223,124 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
); | |||
/// validate datagrid | |||
const validation = useCallback( | |||
(newRow: GridRowModel<PoQcRow>): EntryError => { | |||
(newRow: GridRowModel<QcRow>): 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; | |||
}, | |||
[], | |||
); | |||
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(() => { | |||
console.log(itemDetail); | |||
const status = "receiving"; | |||
@@ -268,10 +375,21 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
{tabIndex == 0 && ( | |||
<> | |||
<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 item xs={4}> | |||
<TextField | |||
type="number" | |||
label={t("acceptedQty")} | |||
fullWidth | |||
/> | |||
@@ -296,7 +414,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
</Grid> | |||
<Grid item xs={12}> | |||
<StyledDataGrid | |||
rows={dummyEscalationHistory} | |||
rows={qcResult} | |||
columns={columns} | |||
onRowSelectionModelChange={(newRowSelectionModel) => { | |||
setRowSelectionModel(newRowSelectionModel); | |||
@@ -1,9 +1,16 @@ | |||
"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 { | |||
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"; | |||
@@ -12,8 +19,8 @@ import StockInForm from "./StockInForm"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import QcFormVer2 from "./QcFormVer2"; | |||
import PutawayForm from "./PutawayForm"; | |||
import { dummyPutawayLine } from "./dummyQcTemplate"; | |||
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | |||
import { useGridApiRef } from "@mui/x-data-grid"; | |||
const style = { | |||
position: "absolute", | |||
top: "50%", | |||
@@ -25,9 +32,8 @@ const style = { | |||
pb: 10, | |||
display: "block", | |||
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"> { | |||
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | |||
@@ -43,12 +49,10 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||
>; | |||
qc?: QcItemWithChecks[]; | |||
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[] }; | |||
} | |||
const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
// type, | |||
@@ -62,165 +66,243 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
qc, | |||
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>({ | |||
defaultValues: { | |||
...itemDetail, | |||
putawayLine: dummyPutawayLine | |||
putawayLine: dummyPutawayLine, | |||
// 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 [openPutaway, setOpenPutaway] = useState(false) | |||
(...args) => { | |||
onClose?.(...args); | |||
// reset(); | |||
}, | |||
[onClose], | |||
); | |||
const [openPutaway, setOpenPutaway] = useState(false); | |||
const onOpenPutaway = useCallback(() => { | |||
setOpenPutaway(true); | |||
}, []); | |||
const onClosePutaway = useCallback(() => { | |||
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) => { | |||
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 | |||
setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||
setValue("status", "received"); | |||
}, []); | |||
}, [setValue]); | |||
useEffect(() => { | |||
console.log(errors); | |||
@@ -97,7 +97,7 @@ const StockInForm: React.FC<Props> = ({ | |||
if (expiryDate) clearErrors(); | |||
if (productionDate) clearErrors(); | |||
}, [productionDate, expiryDate, clearErrors]); | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||