diff --git a/typescript/Frontend/package-lock.json b/typescript/Frontend/package-lock.json index 1bcf739ce..1cdc6cf7e 100644 --- a/typescript/Frontend/package-lock.json +++ b/typescript/Frontend/package-lock.json @@ -26,6 +26,7 @@ "chart.js": "^4.2.1", "css-loader": "^6.7.3", "formik": "^2.2.9", + "mobx-react-lite": "^3.4.3", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dnd": "^16.0.1", @@ -13218,6 +13219,37 @@ "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": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -28150,6 +28182,18 @@ "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": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json index 803841d8f..d1fd92422 100644 --- a/typescript/Frontend/package.json +++ b/typescript/Frontend/package.json @@ -21,6 +21,7 @@ "chart.js": "^4.2.1", "css-loader": "^6.7.3", "formik": "^2.2.9", + "mobx-react-lite": "^3.4.3", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dnd": "^16.0.1", diff --git a/typescript/Frontend/src/Login.tsx b/typescript/Frontend/src/Login.tsx index 6ec794962..6b59b2a10 100644 --- a/typescript/Frontend/src/Login.tsx +++ b/typescript/Frontend/src/Login.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Alert, Button, CircularProgress, Grid } from "@mui/material"; import Container from "@mui/material/Container"; -import axiosConfig, { axiosConfigWithoutToken } from "./config/axiosConfig"; +import { axiosConfigWithoutToken } from "./config/axiosConfig"; import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield"; const loginUser = async (username: string, password: string) => { @@ -17,18 +17,20 @@ const Login = ({ setToken }: { setToken: (value: string) => void }) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(); - const verifyToken = async () => { - axiosConfig.get("/GetAllInstallations"); + const verifyToken = async (token: string) => { + axiosConfigWithoutToken.get("/GetAllInstallations", { + headers: { auth: token }, + }); }; const handleSubmit = () => { setLoading(true); loginUser(username, password).then(({ data }) => { // TODO change this if they return err codes from backend - if (typeof data === "string") { - verifyToken() + if (data && data.token) { + verifyToken(data.token) .then(() => { - setToken(data); + setToken(data.token); setLoading(false); }) .catch((err) => { diff --git a/typescript/Frontend/src/components/Groups/Folder.tsx b/typescript/Frontend/src/components/Groups/Folder.tsx index 3215d7564..c767bc533 100644 --- a/typescript/Frontend/src/components/Groups/Folder.tsx +++ b/typescript/Frontend/src/components/Groups/Folder.tsx @@ -1,10 +1,11 @@ import { Box, CircularProgress, Alert } from "@mui/material"; import { AxiosError } from "axios"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useContext } from "react"; import { useParams } from "react-router-dom"; import axiosConfig from "../../config/axiosConfig"; import { I_Installation } from "../../util/types"; import FolderForm from "./FolderForm"; +import GroupDataContext from "./Tree/GroupDataContext"; const Folder = () => { const { id } = useParams(); diff --git a/typescript/Frontend/src/components/Groups/FolderForm.tsx b/typescript/Frontend/src/components/Groups/FolderForm.tsx index 2dac7aec6..5611dfc38 100644 --- a/typescript/Frontend/src/components/Groups/FolderForm.tsx +++ b/typescript/Frontend/src/components/Groups/FolderForm.tsx @@ -1,9 +1,10 @@ -import { Alert, Button, Grid, Snackbar } from "@mui/material"; +import { Button, CircularProgress, Grid } from "@mui/material"; import { useFormik } from "formik"; import { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import axiosConfig from "../../config/axiosConfig"; import { I_Folder } from "../../util/types"; +import InnovenergySnackbar from "../InnovenergySnackbar"; import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; interface I_CustomerFormProps { @@ -19,6 +20,8 @@ const FolderForm = (props: I_CustomerFormProps) => { const intl = useIntl(); const [snackbarOpen, setSnackbarOpen] = useState(false); + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); const formik = useFormik({ initialValues: { @@ -26,21 +29,25 @@ const FolderForm = (props: I_CustomerFormProps) => { information: values.information, }, onSubmit: (formikValues) => { + setLoading(true); const idAsNumber = parseInt(id, 10); updateFolder({ ...values, ...formikValues, id: idAsNumber, - }).then((res) => { - setSnackbarOpen(true); - }); + }) + .then((res) => { + setSnackbarOpen(true); + setLoading(false); + }) + .catch((err) => { + setSnackbarOpen(true); + setError(err); + setLoading(false); + }); }, }); - const handleClose = () => { - setSnackbarOpen(false); - }; - return (
{ handleChange={formik.handleChange} /> - - - - - - + /> ); }; diff --git a/typescript/Frontend/src/components/Groups/GroupTabs.tsx b/typescript/Frontend/src/components/Groups/GroupTabs.tsx index 06360665c..02e9e72a1 100644 --- a/typescript/Frontend/src/components/Groups/GroupTabs.tsx +++ b/typescript/Frontend/src/components/Groups/GroupTabs.tsx @@ -17,45 +17,51 @@ const GroupTabs = () => { const id = routeMatch?.params?.id; const intl = useIntl(); - return ( - - - - {routeMatch?.pathname.includes("folder") ? ( - - ) : ( - - )} + if (id) { + return ( + + + + {routeMatch?.pathname.includes("folder") ? ( + + ) : ( + + )} - - + + + - - ); + ); + } + return null; }; export default GroupTabs; diff --git a/typescript/Frontend/src/components/Groups/Groups.tsx b/typescript/Frontend/src/components/Groups/Groups.tsx index 3fd71db92..71b0392b8 100644 --- a/typescript/Frontend/src/components/Groups/Groups.tsx +++ b/typescript/Frontend/src/components/Groups/Groups.tsx @@ -1,7 +1,9 @@ import { Grid } from "@mui/material"; import { Container } from "@mui/system"; +import { useState } from "react"; import { Routes, Route } from "react-router"; import routes from "../../routes.json"; +import { I_Folder, I_Installation } from "../../util/types"; import InstallationDetail from "../Installations/Installation"; import NavigationButtons from "../Layout/NavigationButtons"; import Folder from "./Folder"; @@ -9,12 +11,14 @@ import GroupTabs from "./GroupTabs"; import GroupTree from "./Tree/GroupTree"; const Groups = () => { + const [data, setData] = useState<(I_Folder | I_Installation)[]>(); + return ( - + diff --git a/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx index a27d707a9..537c8fa3c 100644 --- a/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/DragPreview.tsx @@ -1,7 +1,7 @@ import { DragLayerMonitorProps } from "@minoru/react-dnd-treeview"; import { I_Installation, I_Folder } from "../../../util/types"; import styles from "./DragPreview.module.scss"; -import TypeIcon from "./TypeIcon"; +import TypeIcon from "../TypeIcon"; interface DragPreviewProps { monitorProps: DragLayerMonitorProps; diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupDataContext.tsx b/typescript/Frontend/src/components/Groups/Tree/GroupDataContext.tsx new file mode 100644 index 000000000..9134d4c9a --- /dev/null +++ b/typescript/Frontend/src/components/Groups/Tree/GroupDataContext.tsx @@ -0,0 +1,13 @@ +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({ + setData: (value) => {}, + data: [], +}); + +export default GroupDataContext; diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss index 11d791fb0..be13fe81b 100644 --- a/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.module.scss @@ -20,6 +20,6 @@ } .treeContainer { - height: 500px; + max-height: 500px; overflow: auto; } diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx index c5b9ffe9d..df8868f10 100644 --- a/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import axiosConfig from "../../../config/axiosConfig"; import { I_Folder, I_Installation } from "../../../util/types"; -import { CircularProgress, Grid } from "@mui/material"; +import { Alert, CircularProgress, Grid } from "@mui/material"; import { DndProvider } from "react-dnd"; import { MultiBackend, @@ -14,6 +14,7 @@ import TreeNode from "./TreeNode"; import styles from "./GroupTree.module.scss"; import withScrolling from "react-dnd-scrolling"; import DragPreview from "./DragPreview"; +import InnovenergySnackbar from "../../InnovenergySnackbar"; const getTreeData = ( data: (I_Folder | I_Installation)[] @@ -30,21 +31,49 @@ const getTreeData = ( }); }; -const GroupTree = () => { - const [data, setData] = useState<(I_Folder | I_Installation)[]>(); +interface GroupTreeProps { + data: (I_Folder | I_Installation)[] | undefined; + setData: (value: (I_Folder | I_Installation)[]) => void; +} + +const GroupTree = (props: GroupTreeProps) => { + const { data, setData } = props; const [loading, setLoading] = useState(false); + const [getError, setGetError] = useState(false); + const [putError, setPutError] = useState(false); + + const [snackbarOpen, setSnackbarOpen] = useState(false); + 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(() => { getData(); - }, []); + }, [getData]); - const getData = async () => { - setLoading(true); - return axiosConfig.get("/GetAllFoldersAndInstallations").then((res) => { - setData(res.data); - setLoading(false); - }); + const findParent = (element: I_Folder | I_Installation): any => { + if (data) { + const parent = data.find( + (el) => el.type === "Folder" && el.id === element.parentId + ); + if (parent) { + return findParent(parent); + } + return element.parentId; + } }; const handleDrop = ( @@ -62,7 +91,13 @@ const GroupTree = () => { } ) .then(() => { + setSnackbarOpen(true); getData(); + }) + .catch((err) => { + setPutError(err); + setLoading(false); + setSnackbarOpen(true); }); }; @@ -107,8 +142,19 @@ const GroupTree = () => { ) => handleDrop(tree, options)} /> + ); + } else if (getError) { + return ( + + Couldn't load data + + ); } return null; }; diff --git a/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx index 3080cab71..5047e07ea 100644 --- a/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx +++ b/typescript/Frontend/src/components/Groups/Tree/TreeNode.tsx @@ -6,7 +6,7 @@ import styles from "./TreeNode.module.scss"; import { Link } from "react-router-dom"; import routes from "../../../routes.json"; import { I_Folder, I_Installation } from "../../../util/types"; -import TypeIcon from "./TypeIcon"; +import TypeIcon from "../TypeIcon"; import DragHandleIcon from "@mui/icons-material/DragHandle"; type Props = { @@ -52,6 +52,7 @@ const TreeNode: React.FC = (props) => { textDecoration: "none", color: "black", }} + draggable={false} >
{node.text} diff --git a/typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx b/typescript/Frontend/src/components/Groups/TypeIcon.tsx similarity index 100% rename from typescript/Frontend/src/components/Groups/Tree/TypeIcon.tsx rename to typescript/Frontend/src/components/Groups/TypeIcon.tsx diff --git a/typescript/Frontend/src/components/InnovenergySnackbar.tsx b/typescript/Frontend/src/components/InnovenergySnackbar.tsx new file mode 100644 index 000000000..5223131b9 --- /dev/null +++ b/typescript/Frontend/src/components/InnovenergySnackbar.tsx @@ -0,0 +1,46 @@ +import { Alert, Snackbar } from "@mui/material"; +import { FormattedMessage } from "react-intl"; + +interface InnovenergySnackbarProps { + open: boolean; + setOpen: (value: boolean) => void; + error?: any; +} +const InnovenergySnackbar = (props: InnovenergySnackbarProps) => { + const { open, setOpen, error } = props; + + const handleClose = () => { + setOpen(false); + }; + return ( + + + {error ? ( + + ) : ( + + )} + + + ); +}; + +export default InnovenergySnackbar; diff --git a/typescript/Frontend/src/lang/de.json b/typescript/Frontend/src/lang/de.json index 80d807f0f..037eeab43 100644 --- a/typescript/Frontend/src/lang/de.json +++ b/typescript/Frontend/src/lang/de.json @@ -16,6 +16,7 @@ "logout": "Logout", "updatedSuccessfully": "Erfolgreich aktualisiert", "groups": "Gruppen", - "group": "Gruppe", - "folder": "Ordner" + "group": "Gruppe", + "folder": "Ordner", + "updateFolderErrorMessage": "Der Ordner konnte nicht aktualisiert werden, ein Fehler ist aufgetreten" } diff --git a/typescript/Frontend/src/lang/en.json b/typescript/Frontend/src/lang/en.json index 856e907aa..45abb09c2 100644 --- a/typescript/Frontend/src/lang/en.json +++ b/typescript/Frontend/src/lang/en.json @@ -17,6 +17,6 @@ "updatedSuccessfully": "Updated successfully", "groups": "Groups", "group": "Group", - "folder": "folder" - + "folder": "folder", + "updateFolderErrorMessage": "Couldn't update folder, an error occured" }