| @@ -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; | |||||