add context for state management for installation and group

This commit is contained in:
Sina Blattmann 2023-03-13 15:17:40 +01:00
parent 3a6f4fc046
commit c5287ce95f
15 changed files with 248 additions and 203 deletions

View File

@ -26,7 +26,6 @@
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"formik": "^2.2.9", "formik": "^2.2.9",
"mobx-react-lite": "^3.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
@ -13219,37 +13218,6 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/mobx": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-6.8.0.tgz",
"integrity": "sha512-+o/DrHa4zykFMSKfS8Z+CPSEg5LW9tSNGTuN8o6MF1GKxlfkSHSeJn5UtgxvPkGgaouplnrLXCF+duAsmm6FHQ==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mobx"
}
},
"node_modules/mobx-react-lite": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz",
"integrity": "sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mobx"
},
"peerDependencies": {
"mobx": "^6.1.0",
"react": "^16.8.0 || ^17 || ^18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -28182,18 +28150,6 @@
"minimist": "^1.2.6" "minimist": "^1.2.6"
} }
}, },
"mobx": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-6.8.0.tgz",
"integrity": "sha512-+o/DrHa4zykFMSKfS8Z+CPSEg5LW9tSNGTuN8o6MF1GKxlfkSHSeJn5UtgxvPkGgaouplnrLXCF+duAsmm6FHQ==",
"peer": true
},
"mobx-react-lite": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz",
"integrity": "sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg==",
"requires": {}
},
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",

View File

@ -21,7 +21,6 @@
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"formik": "^2.2.9", "formik": "^2.2.9",
"mobx-react-lite": "^3.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",

View File

@ -0,0 +1,51 @@
import { createContext, ReactNode, useCallback, useState } from "react";
import axiosConfig from "../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../util/types";
interface GroupContextProviderProps {
data: (I_Folder | I_Installation)[];
setData: (value: (I_Folder | I_Installation)[]) => void;
fetchData: () => Promise<void>;
loading: boolean;
setLoading: (value: boolean) => void;
getError: boolean;
}
export const GroupContext = createContext<GroupContextProviderProps>({
data: [],
setData: () => {},
fetchData: () => Promise.resolve(),
loading: false,
setLoading: () => {},
getError: false,
});
const GroupContextProvider = ({ children }: { children: ReactNode }) => {
const [data, setData] = useState<(I_Folder | I_Installation)[]>([]);
const [loading, setLoading] = useState(false);
const [getError, setGetError] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
return axiosConfig
.get("/GetAllFoldersAndInstallations")
.then((res) => {
setData(res.data);
setLoading(false);
})
.catch((err) => {
setGetError(err);
setLoading(false);
});
}, [setData]);
return (
<GroupContext.Provider
value={{ data, setData, fetchData, loading, setLoading, getError }}
>
{children}
</GroupContext.Provider>
);
};
export default GroupContextProvider;

View File

@ -0,0 +1,43 @@
import { createContext, ReactNode, useCallback, useState } from "react";
import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types";
interface InstallationContextProviderProps {
data: I_Installation[];
setData: (value: I_Installation[]) => void;
fetchData: () => Promise<void>;
loading: boolean;
setLoading: (value: boolean) => void;
}
export const InstallationContext =
createContext<InstallationContextProviderProps>({
data: [],
setData: () => {},
fetchData: () => Promise.resolve(),
loading: false,
setLoading: () => {},
});
const InstallationContextProvider = ({ children }: { children: ReactNode }) => {
const [data, setData] = useState<I_Installation[]>([]);
const [loading, setLoading] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
axiosConfig.get("/GetAllInstallations", {}).then((res) => {
setData(res.data);
setLoading(false);
});
}, []);
return (
<InstallationContext.Provider
value={{ data, setData, fetchData, loading, setLoading }}
>
{children}
</InstallationContext.Provider>
);
};
export default InstallationContextProvider;

View File

@ -1,11 +1,10 @@
import { Box, CircularProgress, Alert } from "@mui/material"; import { Box, CircularProgress, Alert } from "@mui/material";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { useState, useEffect, useContext } from "react"; import { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types"; import { I_Installation } from "../../util/types";
import FolderForm from "./FolderForm"; import FolderForm from "./FolderForm";
import GroupDataContext from "./Tree/GroupDataContext";
const Folder = () => { const Folder = () => {
const { id } = useParams(); const { id } = useParams();

View File

@ -1,11 +1,12 @@
import { Button, CircularProgress, Grid } from "@mui/material"; import { Button, CircularProgress, Grid } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useState } from "react"; import { useContext, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Folder } from "../../util/types"; import { I_Folder } from "../../util/types";
import InnovenergySnackbar from "../InnovenergySnackbar"; import InnovenergySnackbar from "../InnovenergySnackbar";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
import { GroupContext } from "../Context/GroupContextProvider";
interface I_CustomerFormProps { interface I_CustomerFormProps {
values: I_Folder; values: I_Folder;
@ -16,6 +17,8 @@ const updateFolder = (data: I_Folder) => {
return axiosConfig.put("/UpdateFolder", data); return axiosConfig.put("/UpdateFolder", data);
}; };
const FolderForm = (props: I_CustomerFormProps) => { const FolderForm = (props: I_CustomerFormProps) => {
const { fetchData } = useContext(GroupContext);
const { values, id } = props; const { values, id } = props;
const intl = useIntl(); const intl = useIntl();
@ -39,6 +42,7 @@ const FolderForm = (props: I_CustomerFormProps) => {
.then((res) => { .then((res) => {
setSnackbarOpen(true); setSnackbarOpen(true);
setLoading(false); setLoading(false);
fetchData();
}) })
.catch((err) => { .catch((err) => {
setSnackbarOpen(true); setSnackbarOpen(true);

View File

@ -1,38 +1,37 @@
import { Grid } from "@mui/material"; import { Grid } from "@mui/material";
import { Container } from "@mui/system"; import { Container } from "@mui/system";
import { useState } from "react";
import { Routes, Route } from "react-router"; import { Routes, Route } from "react-router";
import routes from "../../routes.json"; import routes from "../../routes.json";
import { I_Folder, I_Installation } from "../../util/types"; import Installation from "../Installations/Installation";
import InstallationDetail from "../Installations/Installation";
import NavigationButtons from "../Layout/NavigationButtons"; import NavigationButtons from "../Layout/NavigationButtons";
import Folder from "./Folder"; import Folder from "./Folder";
import GroupTabs from "./GroupTabs"; import GroupTabs from "./GroupTabs";
import GroupContextProvider from "../Context/GroupContextProvider";
import GroupTree from "./Tree/GroupTree"; import GroupTree from "./Tree/GroupTree";
const Groups = () => { const Groups = () => {
const [data, setData] = useState<(I_Folder | I_Installation)[]>();
return ( return (
<Container maxWidth="xl"> <GroupContextProvider>
<Grid container spacing={2}> <Container maxWidth="xl">
<Grid item xs={3}> <Grid container spacing={2}>
<NavigationButtons /> <Grid item xs={3}>
<GroupTree data={data} setData={setData} /> <NavigationButtons />
<GroupTree />
</Grid>
<Grid item xs={9}>
<GroupTabs />
<Routes>
<Route path={routes.folder + ":id"} element={<Folder />} index />
<Route path={routes.users + ":id"} element={<div>Users</div>} />
<Route
path={routes.installation + ":id"}
element={<Installation />}
/>
</Routes>
</Grid>
</Grid> </Grid>
<Grid item xs={9}> </Container>
<GroupTabs /> </GroupContextProvider>
<Routes>
<Route path={routes.folder + ":id"} element={<Folder />} index />
<Route path={routes.users + ":id"} element={<div>Users</div>} />
<Route
path={routes.installation + ":id"}
element={<InstallationDetail />}
/>
</Routes>
</Grid>
</Grid>
</Container>
); );
}; };

View File

@ -1,13 +0,0 @@
import { createContext } from "react";
import { I_Folder, I_Installation } from "../../../util/types";
interface GroupData {
data: (I_Folder | I_Installation)[] | undefined;
setData: (value: (I_Folder | I_Installation)[]) => void;
}
const GroupDataContext = createContext<GroupData>({
setData: (value) => {},
data: [],
});
export default GroupDataContext;

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react"; import { useEffect, useState, useContext } from "react";
import axiosConfig from "../../../config/axiosConfig"; import axiosConfig from "../../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../../util/types"; import { I_Folder, I_Installation } from "../../../util/types";
import { Alert, CircularProgress, Grid } from "@mui/material"; import { Alert, CircularProgress, Grid } from "@mui/material";
@ -15,6 +15,7 @@ import styles from "./GroupTree.module.scss";
import withScrolling from "react-dnd-scrolling"; import withScrolling from "react-dnd-scrolling";
import DragPreview from "./DragPreview"; import DragPreview from "./DragPreview";
import InnovenergySnackbar from "../../InnovenergySnackbar"; import InnovenergySnackbar from "../../InnovenergySnackbar";
import { GroupContext } from "../../Context/GroupContextProvider";
const getTreeData = ( const getTreeData = (
data: (I_Folder | I_Installation)[] data: (I_Folder | I_Installation)[]
@ -31,40 +32,19 @@ const getTreeData = (
}); });
}; };
interface GroupTreeProps { const GroupTree = () => {
data: (I_Folder | I_Installation)[] | undefined; const { data, fetchData, loading, setLoading, getError } =
setData: (value: (I_Folder | I_Installation)[]) => void; useContext(GroupContext);
}
const GroupTree = (props: GroupTreeProps) => {
const { data, setData } = props;
const [loading, setLoading] = useState(false);
const [getError, setGetError] = useState(false);
const [putError, setPutError] = useState(false); const [putError, setPutError] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarOpen, setSnackbarOpen] = useState(false);
const ScrollingComponent = withScrolling("div"); const ScrollingComponent = withScrolling("div");
const getData = useCallback(async () => {
setLoading(true);
return axiosConfig
.get("/GetAllFoldersAndInstallations")
.then((res) => {
setData(res.data);
setLoading(false);
})
.catch((err) => {
setGetError(err);
setLoading(false);
});
}, [setData]);
useEffect(() => { useEffect(() => {
getData(); fetchData();
}, [getData]); }, [fetchData]);
const findParent = (element: I_Folder | I_Installation): any => { /* const findParent = (element: I_Folder | I_Installation): any => {
if (data) { if (data) {
const parent = data.find( const parent = data.find(
(el) => el.type === "Folder" && el.id === element.parentId (el) => el.type === "Folder" && el.id === element.parentId
@ -74,7 +54,7 @@ const GroupTree = (props: GroupTreeProps) => {
} }
return element.parentId; return element.parentId;
} }
}; }; */
const handleDrop = ( const handleDrop = (
newTree: NodeModel<I_Folder | I_Installation>[], newTree: NodeModel<I_Folder | I_Installation>[],
@ -92,7 +72,7 @@ const GroupTree = (props: GroupTreeProps) => {
) )
.then(() => { .then(() => {
setSnackbarOpen(true); setSnackbarOpen(true);
getData(); fetchData();
}) })
.catch((err) => { .catch((err) => {
setPutError(err); setPutError(err);

View File

@ -34,3 +34,7 @@
.handle > svg { .handle > svg {
pointer-events: none; pointer-events: none;
} }
.selected {
background-color: #f5910014;
}

View File

@ -8,20 +8,27 @@ import routes from "../../../routes.json";
import { I_Folder, I_Installation } from "../../../util/types"; import { I_Folder, I_Installation } from "../../../util/types";
import TypeIcon from "../TypeIcon"; import TypeIcon from "../TypeIcon";
import DragHandleIcon from "@mui/icons-material/DragHandle"; import DragHandleIcon from "@mui/icons-material/DragHandle";
import useRouteMatch from "../../../hooks/useRouteMatch";
type Props = { interface TreeNodeProps {
node: NodeModel<I_Installation | I_Folder>; node: NodeModel<I_Installation | I_Folder>;
depth: number; depth: number;
isOpen: boolean; isOpen: boolean;
onToggle: (id: NodeModel["id"]) => void; onToggle: (id: NodeModel["id"]) => void;
hasChild: boolean; hasChild: boolean;
handleRef: React.RefObject<any>; handleRef: React.RefObject<any>;
}; }
const TreeNode: React.FC<Props> = (props) => { const TreeNode = (props: TreeNodeProps) => {
const { node, isOpen, hasChild, onToggle, depth, handleRef } = props; const { node, isOpen, hasChild, onToggle, depth, handleRef } = props;
const indent = depth * 24; const indent = depth * 24;
const routeMatch = useRouteMatch([
routes.groups + routes.installation + ":id",
routes.groups + routes.folder + ":id",
routes.groups + routes.users + ":id",
]);
const handleToggle = (e: React.MouseEvent) => { const handleToggle = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
onToggle(node.id); onToggle(node.id);
@ -29,7 +36,11 @@ const TreeNode: React.FC<Props> = (props) => {
return ( return (
<div <div
className={`tree-node ${styles.root}`} className={`tree-node ${styles.root} ${
routeMatch?.params.id === node.data?.id.toString()
? styles.selected
: ""
}`}
style={{ paddingInlineStart: indent }} style={{ paddingInlineStart: indent }}
> >
<div <div

View File

@ -1,10 +1,14 @@
import { Alert, Button, Grid, Snackbar } from "@mui/material"; import { Alert, Button, Grid, Snackbar } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useState } from "react"; import { useContext, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import useRouteMatch from "../../hooks/useRouteMatch";
import { I_Installation } from "../../util/types"; import { I_Installation } from "../../util/types";
import { GroupContext } from "../Context/GroupContextProvider";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
import routes from "../../routes.json";
import { InstallationContext } from "../Context/InstallationContextProvider";
interface I_CustomerFormProps { interface I_CustomerFormProps {
values: I_Installation; values: I_Installation;
@ -12,10 +16,16 @@ interface I_CustomerFormProps {
} }
const CustomerForm = (props: I_CustomerFormProps) => { const CustomerForm = (props: I_CustomerFormProps) => {
const { values, id } = props; const { values, id } = props;
const intl = useIntl();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const intl = useIntl();
const { fetchData: fetchGroupData } = useContext(GroupContext);
const { fetchData: fetchInstallationData } = useContext(InstallationContext);
const groupsMatch = useRouteMatch([
routes.groups + routes.installation + ":id",
]);
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
name: values.name, name: values.name,
@ -30,8 +40,13 @@ const CustomerForm = (props: I_CustomerFormProps) => {
...formikValues, ...formikValues,
id, id,
}) })
.then((res) => { .then(() => {
setOpen(true); setOpen(true);
if (groupsMatch) {
fetchGroupData();
} else {
fetchInstallationData();
}
}); });
}, },
}); });

View File

@ -6,7 +6,7 @@ import CustomerForm from "./CustomerForm";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types"; import { I_Installation } from "../../util/types";
const InstallationDetail = () => { const Installation = () => {
const { id } = useParams(); const { id } = useParams();
const [values, setValues] = useState<I_Installation>(); const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -50,4 +50,4 @@ const InstallationDetail = () => {
return null; return null;
}; };
export default InstallationDetail; export default Installation;

View File

@ -5,9 +5,9 @@ import { CircularProgress, Divider, Grid } from "@mui/material";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import useRouteMatch from "../../hooks/useRouteMatch"; import useRouteMatch from "../../hooks/useRouteMatch";
import routes from "../../routes.json"; import routes from "../../routes.json";
import { Fragment, useEffect, useState } from "react"; import { Fragment, useContext, useEffect } from "react";
import { I_Installation } from "../../util/types"; import { I_Installation } from "../../util/types";
import axiosConfig from "../../config/axiosConfig"; import { InstallationContext } from "../Context/InstallationContextProvider";
interface InstallationListProps { interface InstallationListProps {
searchQuery: string; searchQuery: string;
@ -36,8 +36,8 @@ const filterData = (
}; };
const InstallationList = (props: InstallationListProps) => { const InstallationList = (props: InstallationListProps) => {
const [data, setData] = useState<I_Installation[]>(); const { fetchData, data, loading } = useContext(InstallationContext);
const [loading, setLoading] = useState(false);
const filteredData = filterData(props.searchQuery, data); const filteredData = filterData(props.searchQuery, data);
const routeMatch = useRouteMatch([ const routeMatch = useRouteMatch([
@ -48,62 +48,56 @@ const InstallationList = (props: InstallationListProps) => {
]); ]);
useEffect(() => { useEffect(() => {
setLoading(true); fetchData();
axiosConfig.get("/GetAllInstallations", {}).then((res) => { }, [fetchData]);
setData(res.data);
setLoading(false);
});
}, []);
return ( if (loading) {
<> return (
{loading && ( <Grid container justifyContent="center" width="100%">
<Grid container justifyContent="center" width="100%"> <CircularProgress sx={{ m: 6 }} />
<CircularProgress sx={{ m: 6 }} /> </Grid>
</Grid> );
)} }
{data && ( if (data) {
<List return (
sx={{ <List
width: "100%", sx={{
bgcolor: "background.paper", width: "100%",
position: "relative", bgcolor: "background.paper",
overflow: "auto", position: "relative",
maxHeight: 400, overflow: "auto",
py: 0, maxHeight: 400,
mt: 1, py: 0,
}} mt: 1,
component="nav" }}
aria-labelledby="nested-list-subheader" component="nav"
> aria-labelledby="nested-list-subheader"
{filteredData?.map((installation) => { >
return ( {filteredData?.map((installation) => {
<Fragment key={installation.id}> return (
<Link <Fragment key={installation.id}>
to={ <Link
getPathWithoutId(routeMatch?.pattern?.path) + to={
installation.id getPathWithoutId(routeMatch?.pattern?.path) + installation.id
} }
style={{ textDecoration: "none", color: "black" }} style={{ textDecoration: "none", color: "black" }}
>
<ListItemButton
selected={installation.id === Number(routeMatch?.params.id)}
> >
<ListItemButton <ListItemText
selected={installation.id === Number(routeMatch?.params.id)} primary={installation.location + " | " + installation.name}
> />
<ListItemText </ListItemButton>
primary={ </Link>
installation.location + " | " + installation.name <Divider />
} </Fragment>
/> );
</ListItemButton> })}
</Link> </List>
<Divider /> );
</Fragment> }
); return null;
})}
</List>
)}
</>
);
}; };
export default InstallationList; export default InstallationList;

View File

@ -5,34 +5,37 @@ import NavigationButtons from "../Layout/NavigationButtons";
import Sidebar from "../Layout/Sidebar"; import Sidebar from "../Layout/Sidebar";
import BasicTable from "../Layout/Table"; import BasicTable from "../Layout/Table";
import Alarms from "./Alarms"; import Alarms from "./Alarms";
import InstallationDetail from "./Installation"; import Installation from "./Installation";
import InstallationTabs from "./InstallationTabs"; import InstallationTabs from "./InstallationTabs";
import Log from "./Log"; import Log from "./Log";
import routes from "../../routes.json"; import routes from "../../routes.json";
import InstallationContextProvider from "../Context/InstallationContextProvider";
const Installations = () => { const Installations = () => {
return ( return (
<Container maxWidth="xl"> <InstallationContextProvider>
<Grid container spacing={4}> <Container maxWidth="xl">
<Grid item xs={3}> <Grid container spacing={4}>
<NavigationButtons /> <Grid item xs={3}>
<Sidebar /> <NavigationButtons />
<Sidebar />
</Grid>
<Grid item xs={9}>
<InstallationTabs />
<Routes>
<Route
path={routes.installation + ":id"}
element={<Installation />}
index
/>
<Route path={routes.alarms + ":id"} element={<Alarms />} />
<Route path={routes.users + ":id"} element={<BasicTable />} />
<Route path={routes.log + ":id"} element={<Log />} />
</Routes>
</Grid>
</Grid> </Grid>
<Grid item xs={9}> </Container>
<InstallationTabs /> </InstallationContextProvider>
<Routes>
<Route
path={routes.installation + ":id"}
element={<InstallationDetail />}
index
/>
<Route path={routes.alarms + ":id"} element={<Alarms />} />
<Route path={routes.users + ":id"} element={<BasicTable />} />
<Route path={routes.log + ":id"} element={<Log />} />
</Routes>
</Grid>
</Grid>
</Container>
); );
}; };