diff --git a/src/app/(main)/settings/material/page.tsx b/src/app/(main)/settings/material/page.tsx index b2549f7..8be6005 100644 --- a/src/app/(main)/settings/material/page.tsx +++ b/src/app/(main)/settings/material/page.tsx @@ -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 () => { diff --git a/src/app/api/settings/material/actions.ts b/src/app/api/settings/material/actions.ts index c0b7329..14a2676 100644 --- a/src/app/api/settings/material/actions.ts +++ b/src/app/api/settings/material/actions.ts @@ -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) => { diff --git a/src/components/CreateMaterial/CreateMaterialWrapper.tsx b/src/components/CreateMaterial/CreateMaterialWrapper.tsx index 280d0ba..8dd3896 100644 --- a/src/components/CreateMaterial/CreateMaterialWrapper.tsx +++ b/src/components/CreateMaterial/CreateMaterialWrapper.tsx @@ -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 & SubComponents = async (props) => { - console.log(props) + const cookieStore = await cookies() + // console.log("==================cookieStore==================") + // console.log(cookieStore) return } diff --git a/src/components/CreateMaterial/MaterialDetails.tsx b/src/components/CreateMaterial/MaterialDetails.tsx index ba999a6..3f739a6 100644 --- a/src/components/CreateMaterial/MaterialDetails.tsx +++ b/src/components/CreateMaterial/MaterialDetails.tsx @@ -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 = ({ isEditMode }) => { + const { + t, + i18n: { language }, + } = useTranslation(); -const MaterialDetails: React.FC = ({ - isEditMode, -}) => { - const { - t, - i18n: { language }, - } = useTranslation(); + const { + register, + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + getValues, + reset, + resetField, + setError, + clearErrors, + } = useFormContext(); + const columns = useMemo(() => [], []); + const validationTest = (): EntryError => { + const error: EntryError = {}; + return error; + }; + const MaterialTypeInputGridProps: InputDataGridProps< + Partial, + EntryError + > = { + _formKey: "type", + columns, + validation: validationTest, + }; + const MaterialUomInputGridProps: InputDataGridProps< + Partial, + 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(); - return ( - + {t("Material Details")} - - - - + = ({ error={Boolean(errors.name)} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - ); + ); }; export default MaterialDetails; diff --git a/src/components/CreateProject/MilestoneSection.tsx b/src/components/CreateProject/MilestoneSection.tsx index dd4c154..854fc39 100644 --- a/src/components/CreateProject/MilestoneSection.tsx +++ b/src/components/CreateProject/MilestoneSection.tsx @@ -39,6 +39,7 @@ interface Props { declare module "@mui/x-data-grid" { interface FooterPropsOverrides { onAdd: () => void; + child?: React.ReactNode; } } diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index 4901b0a..27820b5 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -7,18 +7,14 @@ import React, { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; type Props = { - // abilities: string[] + } const DashboardPage: React.FC = ({ - // abilities + }) => { const { t } = useTranslation("dashboard"); const router = useRouter(); - // useEffect(()=> { - // window.localStorage.setItem("abilities", JSON.stringify(abilities)) - // }) - return ( <> diff --git a/src/components/MaterialSearch/MaterialSearch.tsx b/src/components/MaterialSearch/MaterialSearch.tsx index b67c98d..e5f1770 100644 --- a/src/components/MaterialSearch/MaterialSearch.tsx +++ b/src/components/MaterialSearch/MaterialSearch.tsx @@ -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 = ({ materials }) => { -const [filteredMaterials, setFilteredMaterials] = useState([]) +const [filteredMaterials, setFilteredMaterials] = useState(materials) const { t } = useTranslation("materials"); const router = useRouter(); @@ -59,6 +60,7 @@ const router = useRouter(); { name: "action", label: t(""), + buttonIcon: , onClick: onDeleteClick, }, diff --git a/src/components/MaterialSearch/MaterialSearchWrapper.tsx b/src/components/MaterialSearch/MaterialSearchWrapper.tsx index 1b48847..1be689b 100644 --- a/src/components/MaterialSearch/MaterialSearchWrapper.tsx +++ b/src/components/MaterialSearch/MaterialSearchWrapper.tsx @@ -7,7 +7,6 @@ interface SubComponents { } const MaterialSearchWrapper: React.FC & SubComponents = async () => { - // const materials: MaterialResult[] = [] const materials = await fetchMaterials(); console.log(materials) diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 3ba835a..a648c96 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -37,7 +37,7 @@ const NavigationContent: React.FC = () => { { icon: , label: "Dashboard", - path: "", + path: "/dashboard", }, { icon: , diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx index 4c82280..72b435b 100644 --- a/src/components/SearchResults/SearchResults.tsx +++ b/src/components/SearchResults/SearchResults.tsx @@ -97,7 +97,7 @@ function SearchResults({ {column.buttonIcon} ) : ( - <>{item[columnName]} + <>{item[columnName] as string} )} ); diff --git a/src/components/useInputDataGrid/backup b/src/components/useInputDataGrid/backup new file mode 100644 index 0000000..b052f76 --- /dev/null +++ b/src/components/useInputDataGrid/backup @@ -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 = Partial< +// T & { +// _isNew: boolean; +// _error: E; +// } +// >; + +// export type InputDataGridProps = { +// formKey: string; +// // items?: TableRow[]; +// columns: GridColDef[]; +// _rowModesModel: GridRowModesModel; +// validation: () => E; +// }; + +// export class ProcessRowUpdateError 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({ +// formKey = "testing", +// // items, +// columns, +// _rowModesModel, +// validation, +// }: InputDataGridProps): { +// DataGrid: React.FC; +// rowModesModel: GridRowModesModel; +// setRowModesModel: Dispatch>; +// } { +// const { +// t, +// // i18n: { language }, +// } = useTranslation(); +// const { setValue, getValues } = useFormContext(); +// const [rowModesModel, setRowModesModel] = +// useState(_rowModesModel); +// const apiRef = useGridApiRef(); +// const getRowId = useCallback>>( +// (row) => row.id!!.toString(), +// [] +// ); +// const list: TableRow[] = getValues(formKey); +// const [rows, setRows] = useState[]>(() => { +// const list: TableRow[] = 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) => { +// const errors = updateError.errors; +// const row = updateError.row; +// console.log(errors); +// apiRef.current.updateRows([{ ...row, _error: errors }]); +// }, +// [apiRef, rowModesModel] +// ); + +// const processRowUpdate = useCallback( +// ( +// newRow: GridRowModel>, +// originalRow: GridRowModel> +// ) => { +// ///////////////// +// // 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; /// 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; +// 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( +// () => [ +// ...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 [ +// } +// label="Save" +// key="edit" +// sx={{ +// color: "primary.main", +// }} +// onClick={handleSave(id)} +// />, +// } +// label="Cancel" +// key="edit" +// onClick={handleCancel(id)} +// />, +// ]; +// } +// return [ +// } +// label="Delete" +// sx={{ +// color: "error.main", +// }} +// onClick={handleDelete(id)} +// color="inherit" +// key="edit" +// />, +// ]; +// }, +// }, +// ], +// [] +// ); + +// // sync useForm +// useEffect(() => { +// setValue(formKey, rows); +// }, [rows]); + +// const footer = ( +// +// +// +// ); +// const DataGrid: React.FC = ({ ...props }) => ( +// } +// 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>) => { +// 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 = ({ child }) => { +// return {child}; +// }; +// const NoRowsOverlay: React.FC = () => { +// const { t } = useTranslation("home"); +// return ( +// +// {t("Add some entries!")} +// +// ); +// }; +// export default useInputDataGrid; diff --git a/src/components/useInputDataGrid/index.ts b/src/components/useInputDataGrid/index.ts new file mode 100644 index 0000000..165f6e6 --- /dev/null +++ b/src/components/useInputDataGrid/index.ts @@ -0,0 +1 @@ +export { default } from "./useInputDataGrid"; diff --git a/src/components/useInputDataGrid/useInputDataGrid.tsx b/src/components/useInputDataGrid/useInputDataGrid.tsx new file mode 100644 index 0000000..2a2ec80 --- /dev/null +++ b/src/components/useInputDataGrid/useInputDataGrid.tsx @@ -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 = Partial< + T & { + _isNew: boolean; + _error: E; + } & ResultWithId +>; + +export type InputDataGridProps = { + _formKey: keyof T; + columns: GridColDef[]; + validation: () => E; +}; + +export class ProcessRowUpdateError 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({ + _formKey, + columns, + validation, +}: InputDataGridProps): { + DataGrid: React.FC; + rowModesModel: GridRowModesModel; + setRowModesModel: Dispatch>; +} { + const { + t, + // i18n: { language }, + } = useTranslation(); + const formKey = _formKey.toString() + const { setValue, getValues } = useFormContext(); + const [rowModesModel, setRowModesModel] = + useState({}); + const apiRef = useGridApiRef(); + const getRowId = useCallback>>( + (row) => row.id!! as number, + [] + ); + const list: TableRow[] = getValues(formKey); + const [rows, setRows] = useState[]>(() => { + const list: TableRow[] = 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) => { + const errors = updateError.errors; + const row = updateError.row; + console.log(errors); + apiRef.current.updateRows([{ ...row, _error: errors }]); + }, + [apiRef, rowModesModel] + ); + + const processRowUpdate = useCallback( + ( + newRow: GridRowModel>, + originalRow: GridRowModel> + ) => { + ///////////////// + // 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; /// 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; + 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( + () => [ + ...columns, + { + field: "actions", + type: "actions", + headerName: "", + width: 100, + cellClassName: "actions", + getActions: ({ id }: { id: GridRowId }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; + if (isInEditMode) { + return [ + } + label="Save" + key="edit" + sx={{ + color: "primary.main", + }} + onClick={handleSave(id)} + />, + } + label="Cancel" + key="edit" + onClick={handleCancel(id)} + />, + ]; + } + return [ + } + 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 = ( + + + + + ); + const DataGrid: React.FC = (props) => ( + } + 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>) => { + 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 = ({ child }) => { + return {child}; +}; +const NoRowsOverlay: React.FC = () => { + const { t } = useTranslation("home"); + return ( + + {t("Add some entries!")} + + ); +}; +export default useInputDataGrid;