@@ -9,7 +9,7 @@ import Link from "next/link"; | |||||
import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Claims", | |||||
title: "Material", | |||||
}; | }; | ||||
const materialSetting: React.FC = async () => { | const materialSetting: React.FC = async () => { | ||||
@@ -4,7 +4,24 @@ import { revalidateTag } from "next/cache"; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
export type CreateMaterialInputs = { | export type CreateMaterialInputs = { | ||||
id?: string | number | |||||
code: string; | |||||
name: string; | name: string; | ||||
isConsumables: boolean; | |||||
description?: string | undefined; | |||||
type?: string | undefined; | |||||
remarks?: string | undefined; | |||||
shelfLife?: Number | undefined; | |||||
countryOfOrigin?: string | undefined; | |||||
minHumid?: number | undefined; | |||||
maxHumid?: number | undefined; | |||||
minTemp?: number | undefined; | |||||
maxTemp?: number | undefined; | |||||
sampleRate?: number | undefined; | |||||
passingRate?: number | undefined; | |||||
netWeight?: number | undefined; | |||||
uom?: any; | |||||
weightUnit?: any; | |||||
} | } | ||||
export const saveMaterial = async (data: CreateMaterialInputs) => { | export const saveMaterial = async (data: CreateMaterialInputs) => { | ||||
@@ -1,6 +1,6 @@ | |||||
import CreateStaff from "./CreateMaterial"; | import CreateStaff from "./CreateMaterial"; | ||||
import CreateMaterialLoading from "./CreateMaterialLoading"; | import CreateMaterialLoading from "./CreateMaterialLoading"; | ||||
import { cookies } from 'next/headers' | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof CreateMaterialLoading; | Loading: typeof CreateMaterialLoading; | ||||
} | } | ||||
@@ -12,7 +12,9 @@ type CreateMaterialProps = { | |||||
type Props = CreateMaterialProps | type Props = CreateMaterialProps | ||||
const CreateMaterialWrapper: React.FC<Props> & SubComponents = async (props) => { | const CreateMaterialWrapper: React.FC<Props> & SubComponents = async (props) => { | ||||
console.log(props) | |||||
const cookieStore = await cookies() | |||||
// console.log("==================cookieStore==================") | |||||
// console.log(cookieStore) | |||||
return <CreateStaff isEditMode={Boolean(props.isEditMode)}/> | return <CreateStaff isEditMode={Boolean(props.isEditMode)}/> | ||||
} | } | ||||
@@ -1,54 +1,87 @@ | |||||
"use client"; | "use client"; | ||||
import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; | import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; | ||||
import { Box, Card, CardContent, Grid, Stack, TextField, Typography } from "@mui/material"; | |||||
import { | |||||
Box, | |||||
Card, | |||||
CardContent, | |||||
Grid, | |||||
Stack, | |||||
TextField, | |||||
Typography, | |||||
} from "@mui/material"; | |||||
import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import ControlledAutoComplete from "../ControlledAutoComplete"; | import ControlledAutoComplete from "../ControlledAutoComplete"; | ||||
import InputDataGrid from "../useInputDataGrid"; | |||||
import useInputDataGrid from "../useInputDataGrid"; | |||||
import { useMemo, useState } from "react"; | |||||
import { GridColDef, GridRowModesModel } from "@mui/x-data-grid"; | |||||
import { | |||||
InputDataGridProps, | |||||
ResultWithId, | |||||
} from "../useInputDataGrid/useInputDataGrid"; | |||||
type Props = { | type Props = { | ||||
isEditMode: boolean; | isEditMode: boolean; | ||||
}; | }; | ||||
export type EntryError = { | |||||
[field in keyof CreateMaterialInputs]?: string; | |||||
}; | |||||
const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation(); | |||||
const MaterialDetails: React.FC<Props> = ({ | |||||
isEditMode, | |||||
}) => { | |||||
const { | |||||
t, | |||||
i18n: { language }, | |||||
} = useTranslation(); | |||||
const { | |||||
register, | |||||
formState: { errors, defaultValues, touchedFields }, | |||||
watch, | |||||
control, | |||||
setValue, | |||||
getValues, | |||||
reset, | |||||
resetField, | |||||
setError, | |||||
clearErrors, | |||||
} = useFormContext<CreateMaterialInputs>(); | |||||
const columns = useMemo<GridColDef[]>(() => [], []); | |||||
const validationTest = (): EntryError => { | |||||
const error: EntryError = {}; | |||||
return error; | |||||
}; | |||||
const MaterialTypeInputGridProps: InputDataGridProps< | |||||
Partial<CreateMaterialInputs>, | |||||
EntryError | |||||
> = { | |||||
_formKey: "type", | |||||
columns, | |||||
validation: validationTest, | |||||
}; | |||||
const MaterialUomInputGridProps: InputDataGridProps< | |||||
Partial<CreateMaterialInputs>, | |||||
EntryError | |||||
> = { | |||||
_formKey: "uom", | |||||
columns, | |||||
validation: validationTest, | |||||
}; | |||||
const { DataGrid: MaterialTypeInputGrid } = useInputDataGrid( | |||||
MaterialTypeInputGridProps | |||||
); | |||||
const { DataGrid: MaterialUomInputGrid } = useInputDataGrid( | |||||
MaterialUomInputGridProps | |||||
); | |||||
const { | |||||
register, | |||||
formState: { errors, defaultValues, touchedFields }, | |||||
watch, | |||||
control, | |||||
setValue, | |||||
getValues, | |||||
reset, | |||||
resetField, | |||||
setError, | |||||
clearErrors | |||||
} = useFormContext<CreateMaterialInputs>(); | |||||
return ( | return ( | ||||
<Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
<CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
<Box> | <Box> | ||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Material Details")} | {t("Material Details")} | ||||
</Typography> | </Typography> | ||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
<Grid item xs={6}> | |||||
<ControlledAutoComplete | |||||
control={control} | |||||
options={[]} | |||||
name="name" | |||||
label={t("name")} | |||||
noOptionsText={t("No Option")} | |||||
// disabled={isEditMode} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<Grid item xs={6}> | |||||
<TextField | <TextField | ||||
label={t("Name")} | label={t("Name")} | ||||
fullWidth | fullWidth | ||||
@@ -58,10 +91,137 @@ const MaterialDetails: React.FC<Props> = ({ | |||||
error={Boolean(errors.name)} | error={Boolean(errors.name)} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: "code required!", | |||||
})} | |||||
error={Boolean(errors.code)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("description")} | |||||
fullWidth | |||||
{...register("description", { | |||||
required: "description required!", | |||||
})} | |||||
error={Boolean(errors.description)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("shelfLife")} | |||||
fullWidth | |||||
{...register("shelfLife", { | |||||
required: "shelfLife required!", | |||||
})} | |||||
error={Boolean(errors.shelfLife)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("countryOfOrigin")} | |||||
fullWidth | |||||
{...register("countryOfOrigin", { | |||||
required: "countryOfOrigin required!", | |||||
})} | |||||
error={Boolean(errors.countryOfOrigin)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("minHumid")} | |||||
fullWidth | |||||
{...register("minHumid", { | |||||
required: "minHumid required!", | |||||
})} | |||||
error={Boolean(errors.minHumid)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("maxHumid")} | |||||
fullWidth | |||||
{...register("maxHumid", { | |||||
required: "maxHumid required!", | |||||
})} | |||||
error={Boolean(errors.maxHumid)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("minTemp")} | |||||
fullWidth | |||||
{...register("minTemp", { | |||||
required: "minTemp required!", | |||||
})} | |||||
error={Boolean(errors.minTemp)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("maxTemp")} | |||||
fullWidth | |||||
{...register("maxTemp", { | |||||
required: "maxTemp required!", | |||||
})} | |||||
error={Boolean(errors.maxTemp)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("sampleRate")} | |||||
fullWidth | |||||
{...register("sampleRate", { | |||||
required: "sampleRate required!", | |||||
})} | |||||
error={Boolean(errors.sampleRate)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("passingRate")} | |||||
fullWidth | |||||
{...register("passingRate", { | |||||
required: "passingRate required!", | |||||
})} | |||||
error={Boolean(errors.passingRate)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6} /> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("remarks")} | |||||
fullWidth | |||||
{...register("remarks", { | |||||
required: "remarks required!", | |||||
})} | |||||
error={Boolean(errors.remarks)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("netWeight")} | |||||
fullWidth | |||||
{...register("netWeight", { | |||||
required: "netWeight required!", | |||||
})} | |||||
error={Boolean(errors.netWeight)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<MaterialTypeInputGrid /> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<MaterialUomInputGrid /> | |||||
</Grid> | |||||
</Grid> | </Grid> | ||||
</Box> | </Box> | ||||
</CardContent> | </CardContent> | ||||
</Card> | </Card> | ||||
); | |||||
); | |||||
}; | }; | ||||
export default MaterialDetails; | export default MaterialDetails; |
@@ -39,6 +39,7 @@ interface Props { | |||||
declare module "@mui/x-data-grid" { | declare module "@mui/x-data-grid" { | ||||
interface FooterPropsOverrides { | interface FooterPropsOverrides { | ||||
onAdd: () => void; | onAdd: () => void; | ||||
child?: React.ReactNode; | |||||
} | } | ||||
} | } | ||||
@@ -7,18 +7,14 @@ import React, { useCallback, useEffect, useState } from "react"; | |||||
import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
type Props = { | type Props = { | ||||
// abilities: string[] | |||||
} | } | ||||
const DashboardPage: React.FC<Props> = ({ | const DashboardPage: React.FC<Props> = ({ | ||||
// abilities | |||||
}) => { | }) => { | ||||
const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
// useEffect(()=> { | |||||
// window.localStorage.setItem("abilities", JSON.stringify(abilities)) | |||||
// }) | |||||
return ( | return ( | ||||
<ThemeProvider theme={theme}> | <ThemeProvider theme={theme}> | ||||
<> | <> | ||||
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import { EditNote } from "@mui/icons-material"; | import { EditNote } from "@mui/icons-material"; | ||||
import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
import { GridDeleteIcon } from "@mui/x-data-grid"; | |||||
type Props = { | type Props = { | ||||
materials: MaterialResult[] | materials: MaterialResult[] | ||||
@@ -17,7 +18,7 @@ type SearchParamNames = keyof SearchQuery; | |||||
const MaterialSearch: React.FC<Props> = ({ | const MaterialSearch: React.FC<Props> = ({ | ||||
materials | materials | ||||
}) => { | }) => { | ||||
const [filteredMaterials, setFilteredMaterials] = useState<MaterialResult[]>([]) | |||||
const [filteredMaterials, setFilteredMaterials] = useState<MaterialResult[]>(materials) | |||||
const { t } = useTranslation("materials"); | const { t } = useTranslation("materials"); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
@@ -59,6 +60,7 @@ const router = useRouter(); | |||||
{ | { | ||||
name: "action", | name: "action", | ||||
label: t(""), | label: t(""), | ||||
buttonIcon: <GridDeleteIcon />, | |||||
onClick: onDeleteClick, | onClick: onDeleteClick, | ||||
}, | }, | ||||
@@ -7,7 +7,6 @@ interface SubComponents { | |||||
} | } | ||||
const MaterialSearchWrapper: React.FC & SubComponents = async () => { | const MaterialSearchWrapper: React.FC & SubComponents = async () => { | ||||
// const materials: MaterialResult[] = [] | |||||
const materials = await fetchMaterials(); | const materials = await fetchMaterials(); | ||||
console.log(materials) | console.log(materials) | ||||
@@ -37,7 +37,7 @@ const NavigationContent: React.FC = () => { | |||||
{ | { | ||||
icon: <Dashboard />, | icon: <Dashboard />, | ||||
label: "Dashboard", | label: "Dashboard", | ||||
path: "", | |||||
path: "/dashboard", | |||||
}, | }, | ||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
@@ -97,7 +97,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
{column.buttonIcon} | {column.buttonIcon} | ||||
</IconButton> | </IconButton> | ||||
) : ( | ) : ( | ||||
<>{item[columnName]}</> | |||||
<>{item[columnName] as string}</> | |||||
)} | )} | ||||
</TableCell> | </TableCell> | ||||
); | ); | ||||
@@ -0,0 +1,307 @@ | |||||
// import { | |||||
// Dispatch, | |||||
// SetStateAction, | |||||
// useCallback, | |||||
// useEffect, | |||||
// useMemo, | |||||
// useState, | |||||
// } from "react"; | |||||
// import StyledDataGrid from "../StyledDataGrid"; | |||||
// import { | |||||
// FooterPropsOverrides, | |||||
// GridActionsCellItem, | |||||
// GridCellParams, | |||||
// GridColDef, | |||||
// GridRowId, | |||||
// GridRowIdGetter, | |||||
// GridRowModel, | |||||
// GridRowModes, | |||||
// GridRowModesModel, | |||||
// GridToolbarContainer, | |||||
// GridValidRowModel, | |||||
// useGridApiRef, | |||||
// } from "@mui/x-data-grid"; | |||||
// import { 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"; | |||||
// export interface ResultWithId { | |||||
// id: string | number; | |||||
// } | |||||
// export type TableRow<T extends ResultWithId, E> = Partial< | |||||
// T & { | |||||
// _isNew: boolean; | |||||
// _error: E; | |||||
// } | |||||
// >; | |||||
// export type InputDataGridProps<T extends ResultWithId, E> = { | |||||
// formKey: string; | |||||
// // items?: TableRow<T, E>[]; | |||||
// columns: GridColDef[]; | |||||
// _rowModesModel: GridRowModesModel; | |||||
// validation: () => 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); | |||||
// } | |||||
// } | |||||
// function useInputDataGrid<T extends ResultWithId, E>({ | |||||
// formKey = "testing", | |||||
// // items, | |||||
// columns, | |||||
// _rowModesModel, | |||||
// validation, | |||||
// }: InputDataGridProps<T, E>): { | |||||
// DataGrid: React.FC; | |||||
// rowModesModel: GridRowModesModel; | |||||
// setRowModesModel: Dispatch<SetStateAction<GridRowModesModel>>; | |||||
// } { | |||||
// const { | |||||
// t, | |||||
// // i18n: { language }, | |||||
// } = useTranslation(); | |||||
// const { setValue, getValues } = useFormContext(); | |||||
// const [rowModesModel, setRowModesModel] = | |||||
// useState<GridRowModesModel>(_rowModesModel); | |||||
// const apiRef = useGridApiRef(); | |||||
// const getRowId = useCallback<GridRowIdGetter<TableRow<T, E>>>( | |||||
// (row) => row.id!!.toString(), | |||||
// [] | |||||
// ); | |||||
// const list: TableRow<T, E>[] = getValues(formKey); | |||||
// const [rows, setRows] = useState<TableRow<T, E>[]>(() => { | |||||
// const list: TableRow<T, E>[] = getValues(formKey); | |||||
// return list && list.length > 0 ? list : []; | |||||
// }); | |||||
// const originalRows = list && list.length > 0 ? list : []; | |||||
// const handleSave = useCallback( | |||||
// (id: GridRowId) => () => { | |||||
// setRowModesModel((prevRowModesModel) => ({ | |||||
// ...prevRowModesModel, | |||||
// [id]: { mode: GridRowModes.View }, | |||||
// })); | |||||
// }, | |||||
// [setRowModesModel] | |||||
// ); | |||||
// 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, rowModesModel] | |||||
// ); | |||||
// const processRowUpdate = useCallback( | |||||
// ( | |||||
// newRow: GridRowModel<TableRow<T, E>>, | |||||
// originalRow: GridRowModel<TableRow<T, E>> | |||||
// ) => { | |||||
// ///////////////// | |||||
// // validation here | |||||
// // const errors = validation(); //repace true with validation method | |||||
// // if (errors) { | |||||
// // throw new ProcessRowUpdateError( | |||||
// // originalRow, | |||||
// // "validation error", | |||||
// // errors, | |||||
// // ); | |||||
// // } | |||||
// ///////////////// | |||||
// const { _isNew, _error, ...updatedRow } = newRow; | |||||
// const rowToSave = { | |||||
// ...updatedRow, | |||||
// } as TableRow<T, E>; /// test | |||||
// setRows((rw) => | |||||
// rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | |||||
// ); | |||||
// return rowToSave; | |||||
// }, | |||||
// [rows] | |||||
// ); | |||||
// const addRow = useCallback(() => { | |||||
// const newEntry = { id: Date.now(), _isNew: true } as TableRow<T, E>; | |||||
// setRows((prev) => [...prev, newEntry]); | |||||
// setRowModesModel((model) => ({ | |||||
// ...model, | |||||
// [getRowId(newEntry)]: { | |||||
// mode: GridRowModes.Edit, | |||||
// // fieldToFocus: "team", /// test | |||||
// }, | |||||
// })); | |||||
// }, [getRowId]); | |||||
// const handleCancel = useCallback( | |||||
// (id: GridRowId) => () => { | |||||
// setRowModesModel((model) => ({ | |||||
// ...model, | |||||
// [id]: { mode: GridRowModes.View, ignoreModifications: true }, | |||||
// })); | |||||
// const editedRow = rows.find((row) => getRowId(row) === id); | |||||
// console.log(editedRow); | |||||
// if (editedRow?._isNew) { | |||||
// setRows((rw) => rw.filter((r) => r.id !== id)); | |||||
// } else { | |||||
// setRows((rw) => | |||||
// rw.map((r) => (getRowId(r) === id ? { ...r, _error: undefined } : r)) | |||||
// ); | |||||
// } | |||||
// }, | |||||
// [setRowModesModel, rows] | |||||
// ); | |||||
// const handleDelete = useCallback( | |||||
// (id: GridRowId) => () => { | |||||
// setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | |||||
// }, | |||||
// [] | |||||
// ); | |||||
// const _columns = useMemo<GridColDef[]>( | |||||
// () => [ | |||||
// ...columns, | |||||
// { | |||||
// field: "actions", | |||||
// type: "actions", | |||||
// headerName: "edit", | |||||
// width: 100, | |||||
// 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" | |||||
// />, | |||||
// ]; | |||||
// }, | |||||
// }, | |||||
// ], | |||||
// [] | |||||
// ); | |||||
// // sync useForm | |||||
// useEffect(() => { | |||||
// setValue(formKey, rows); | |||||
// }, [rows]); | |||||
// const footer = ( | |||||
// <Box display="flex" gap={2} alignItems="center"> | |||||
// <Button | |||||
// disableRipple | |||||
// variant="outlined" | |||||
// startIcon={<Add />} | |||||
// onClick={addRow} | |||||
// size="small" | |||||
// > | |||||
// {t("Add Record")} | |||||
// </Button> | |||||
// </Box> | |||||
// ); | |||||
// const DataGrid: React.FC = ({ ...props }) => ( | |||||
// <StyledDataGrid | |||||
// getRowId={getRowId as GridRowIdGetter<GridValidRowModel>} | |||||
// 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 | |||||
// rowModesModel={rowModesModel} | |||||
// onRowModesModelChange={setRowModesModel} | |||||
// processRowUpdate={processRowUpdate as any} | |||||
// 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 }, | |||||
// }} | |||||
// /> | |||||
// ); | |||||
// return { | |||||
// DataGrid, | |||||
// rowModesModel, | |||||
// setRowModesModel, | |||||
// }; | |||||
// } | |||||
// 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 useInputDataGrid; |
@@ -0,0 +1 @@ | |||||
export { default } from "./useInputDataGrid"; |
@@ -0,0 +1,326 @@ | |||||
import { | |||||
Dispatch, | |||||
SetStateAction, | |||||
useCallback, | |||||
useEffect, | |||||
useMemo, | |||||
useState, | |||||
} from "react"; | |||||
import StyledDataGrid from "../StyledDataGrid"; | |||||
import { | |||||
FooterPropsOverrides, | |||||
GridActionsCellItem, | |||||
GridCellParams, | |||||
GridColDef, | |||||
GridRowId, | |||||
GridRowIdGetter, | |||||
GridRowModel, | |||||
GridRowModes, | |||||
GridRowModesModel, | |||||
GridToolbarContainer, | |||||
GridValidRowModel, | |||||
useGridApiRef, | |||||
} from "@mui/x-data-grid"; | |||||
import { 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"; | |||||
export interface ResultWithId { | |||||
id: string | number; | |||||
} | |||||
type DataGridProps = { | |||||
[key: string]: any | |||||
} | |||||
export type TableRow<T, E> = Partial< | |||||
T & { | |||||
_isNew: boolean; | |||||
_error: E; | |||||
} & ResultWithId | |||||
>; | |||||
export type InputDataGridProps<T, E> = { | |||||
_formKey: keyof T; | |||||
columns: GridColDef[]; | |||||
validation: () => 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); | |||||
} | |||||
} | |||||
function useInputDataGrid<T, E>({ | |||||
_formKey, | |||||
columns, | |||||
validation, | |||||
}: InputDataGridProps<T, E>): { | |||||
DataGrid: React.FC; | |||||
rowModesModel: GridRowModesModel; | |||||
setRowModesModel: Dispatch<SetStateAction<GridRowModesModel>>; | |||||
} { | |||||
const { | |||||
t, | |||||
// i18n: { language }, | |||||
} = useTranslation(); | |||||
const formKey = _formKey.toString() | |||||
const { setValue, getValues } = useFormContext(); | |||||
const [rowModesModel, setRowModesModel] = | |||||
useState<GridRowModesModel>({}); | |||||
const apiRef = useGridApiRef(); | |||||
const getRowId = useCallback<GridRowIdGetter<TableRow<T, E>>>( | |||||
(row) => row.id!! as number, | |||||
[] | |||||
); | |||||
const list: TableRow<T, E>[] = getValues(formKey); | |||||
const [rows, setRows] = useState<TableRow<T, E>[]>(() => { | |||||
const list: TableRow<T, E>[] = getValues(formKey); | |||||
return list && list.length > 0 ? list : []; | |||||
}); | |||||
const originalRows = list && list.length > 0 ? list : []; | |||||
const handleSave = useCallback( | |||||
(id: GridRowId) => () => { | |||||
setRowModesModel((prevRowModesModel) => ({ | |||||
...prevRowModesModel, | |||||
[id]: { mode: GridRowModes.View }, | |||||
})); | |||||
}, | |||||
[setRowModesModel] | |||||
); | |||||
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, rowModesModel] | |||||
); | |||||
const processRowUpdate = useCallback( | |||||
( | |||||
newRow: GridRowModel<TableRow<T, E>>, | |||||
originalRow: GridRowModel<TableRow<T, E>> | |||||
) => { | |||||
///////////////// | |||||
// validation here | |||||
// const errors = validation(); //repace true with validation method | |||||
// if (errors) { | |||||
// throw new ProcessRowUpdateError( | |||||
// originalRow, | |||||
// "validation error", | |||||
// errors, | |||||
// ); | |||||
// } | |||||
///////////////// | |||||
const { _isNew, _error, ...updatedRow } = newRow; | |||||
const rowToSave = { | |||||
...updatedRow, | |||||
} as TableRow<T, E>; /// test | |||||
setRows((rw) => | |||||
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | |||||
); | |||||
return rowToSave; | |||||
}, | |||||
[rows] | |||||
); | |||||
const addRow = useCallback(() => { | |||||
const newEntry = { id: Date.now(), _isNew: true } as TableRow<T, E>; | |||||
setRows((prev) => [...prev, newEntry]); | |||||
// console.log(newEntry) | |||||
setRowModesModel((model) => ({ | |||||
...model, | |||||
[getRowId(newEntry)]: { | |||||
mode: GridRowModes.Edit, | |||||
// fieldToFocus: "team", /// test | |||||
}, | |||||
})); | |||||
}, [getRowId]); | |||||
const reset = useCallback(() => { | |||||
setRowModesModel({}) | |||||
setRows([]) | |||||
}, []); | |||||
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)) | |||||
); | |||||
} | |||||
}, | |||||
[setRowModesModel, rows] | |||||
); | |||||
useEffect(() => { | |||||
console.log(rows) | |||||
}, [rows]) | |||||
const handleDelete = useCallback( | |||||
(id: GridRowId) => () => { | |||||
setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | |||||
}, | |||||
[] | |||||
); | |||||
if (columns) { | |||||
} | |||||
const _columns = useMemo<GridColDef[]>( | |||||
() => [ | |||||
...columns, | |||||
{ | |||||
field: "actions", | |||||
type: "actions", | |||||
headerName: "", | |||||
width: 100, | |||||
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" | |||||
/>, | |||||
]; | |||||
}, | |||||
}, | |||||
], | |||||
[rowModesModel, handleSave, handleCancel, handleDelete] | |||||
); | |||||
// sync useForm | |||||
useEffect(() => { | |||||
setValue(formKey, rows); | |||||
}, [rows]); | |||||
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 DataGrid: React.FC<DataGridProps> = (props) => ( | |||||
<StyledDataGrid | |||||
{...props} | |||||
getRowId={getRowId as GridRowIdGetter<GridValidRowModel>} | |||||
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 | |||||
rowModesModel={rowModesModel} | |||||
onRowModesModelChange={setRowModesModel} | |||||
processRowUpdate={processRowUpdate as any} | |||||
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 }, | |||||
}} | |||||
/> | |||||
); | |||||
return { | |||||
DataGrid, | |||||
rowModesModel, | |||||
setRowModesModel, | |||||
}; | |||||
} | |||||
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 useInputDataGrid; |