| @@ -9,7 +9,7 @@ import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| export const metadata: Metadata = { | |||
| title: "Claims", | |||
| title: "Material", | |||
| }; | |||
| const materialSetting: React.FC = async () => { | |||
| @@ -4,7 +4,24 @@ import { revalidateTag } from "next/cache"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| export type CreateMaterialInputs = { | |||
| id?: string | number | |||
| code: 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) => { | |||
| @@ -1,6 +1,6 @@ | |||
| import CreateStaff from "./CreateMaterial"; | |||
| import CreateMaterialLoading from "./CreateMaterialLoading"; | |||
| import { cookies } from 'next/headers' | |||
| interface SubComponents { | |||
| Loading: typeof CreateMaterialLoading; | |||
| } | |||
| @@ -12,7 +12,9 @@ type CreateMaterialProps = { | |||
| type Props = CreateMaterialProps | |||
| 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)}/> | |||
| } | |||
| @@ -1,54 +1,87 @@ | |||
| "use client"; | |||
| 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 { useTranslation } from "react-i18next"; | |||
| 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 = { | |||
| 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 ( | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| <Box> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {t("Material Details")} | |||
| </Typography> | |||
| <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 | |||
| label={t("Name")} | |||
| fullWidth | |||
| @@ -58,10 +91,137 @@ const MaterialDetails: React.FC<Props> = ({ | |||
| error={Boolean(errors.name)} | |||
| /> | |||
| </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> | |||
| </Box> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| ); | |||
| }; | |||
| export default MaterialDetails; | |||
| @@ -39,6 +39,7 @@ interface Props { | |||
| declare module "@mui/x-data-grid" { | |||
| interface FooterPropsOverrides { | |||
| onAdd: () => void; | |||
| child?: React.ReactNode; | |||
| } | |||
| } | |||
| @@ -7,18 +7,14 @@ import React, { useCallback, useEffect, useState } from "react"; | |||
| import { useRouter } from "next/navigation"; | |||
| type Props = { | |||
| // abilities: string[] | |||
| } | |||
| const DashboardPage: React.FC<Props> = ({ | |||
| // abilities | |||
| }) => { | |||
| const { t } = useTranslation("dashboard"); | |||
| const router = useRouter(); | |||
| // useEffect(()=> { | |||
| // window.localStorage.setItem("abilities", JSON.stringify(abilities)) | |||
| // }) | |||
| return ( | |||
| <ThemeProvider theme={theme}> | |||
| <> | |||
| @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; | |||
| import SearchResults, { Column } from "../SearchResults"; | |||
| import { EditNote } from "@mui/icons-material"; | |||
| import { useRouter, useSearchParams } from "next/navigation"; | |||
| import { GridDeleteIcon } from "@mui/x-data-grid"; | |||
| type Props = { | |||
| materials: MaterialResult[] | |||
| @@ -17,7 +18,7 @@ type SearchParamNames = keyof SearchQuery; | |||
| const MaterialSearch: React.FC<Props> = ({ | |||
| materials | |||
| }) => { | |||
| const [filteredMaterials, setFilteredMaterials] = useState<MaterialResult[]>([]) | |||
| const [filteredMaterials, setFilteredMaterials] = useState<MaterialResult[]>(materials) | |||
| const { t } = useTranslation("materials"); | |||
| const router = useRouter(); | |||
| @@ -59,6 +60,7 @@ const router = useRouter(); | |||
| { | |||
| name: "action", | |||
| label: t(""), | |||
| buttonIcon: <GridDeleteIcon />, | |||
| onClick: onDeleteClick, | |||
| }, | |||
| @@ -7,7 +7,6 @@ interface SubComponents { | |||
| } | |||
| const MaterialSearchWrapper: React.FC & SubComponents = async () => { | |||
| // const materials: MaterialResult[] = [] | |||
| const materials = await fetchMaterials(); | |||
| console.log(materials) | |||
| @@ -37,7 +37,7 @@ const NavigationContent: React.FC = () => { | |||
| { | |||
| icon: <Dashboard />, | |||
| label: "Dashboard", | |||
| path: "", | |||
| path: "/dashboard", | |||
| }, | |||
| { | |||
| icon: <RequestQuote />, | |||
| @@ -97,7 +97,7 @@ function SearchResults<T extends ResultWithId>({ | |||
| {column.buttonIcon} | |||
| </IconButton> | |||
| ) : ( | |||
| <>{item[columnName]}</> | |||
| <>{item[columnName] as string}</> | |||
| )} | |||
| </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; | |||