Fix a few small bugs, revert Detail Component

This commit is contained in:
Sina Blattmann 2023-04-03 15:55:50 +02:00
parent 156eee6c9c
commit 30c9752f9a
35 changed files with 447 additions and 215 deletions

View File

@ -7,7 +7,6 @@ import { IntlProvider } from "react-intl";
import { useState } from "react";
import en from "./lang/en.json";
import de from "./lang/de.json";
import Installations from "./components/Installations/Installations";
import LanguageSelect from "./components/Layout/LanguageSelect";
import LogoutButton from "./components/Layout/LogoutButton";
import Users from "./components/Users/Users";
@ -36,7 +35,7 @@ const App = () => {
locale={language}
defaultLocale="EN"
>
<Container maxWidth="xl" sx={{ margin: 2 }}>
<Container maxWidth="xl" sx={{ marginTop: 2 }}>
<Grid container spacing={2}>
<Grid item xs={3}>
<NavigationButtons />

View File

@ -1,8 +1,9 @@
import React, { useState } from "react";
import { Alert, Button, CircularProgress, Grid } from "@mui/material";
import { Alert, CircularProgress, Grid } from "@mui/material";
import Container from "@mui/material/Container";
import { axiosConfigWithoutToken } from "./config/axiosConfig";
import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield";
import InnovenergyButton from "./components/Layout/InnovenergyButton";
const loginUser = async (username: string, password: string) => {
return axiosConfigWithoutToken.post("/Login", null, {
@ -67,9 +68,9 @@ const Login = ({ setToken, setLanguage }: I_LoginProps) => {
/>
{error && <Alert severity="error">Incorrect username or password</Alert>}
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<Button variant="outlined" onClick={handleSubmit} sx={{ my: 1 }}>
<InnovenergyButton onClick={handleSubmit} sx={{ my: 1 }}>
Login
</Button>
</InnovenergyButton>
</Grid>
{loading && <CircularProgress />}
</Container>

View File

@ -11,26 +11,26 @@ import axiosConfig from "../../config/axiosConfig";
import { I_User, UserWithInheritedAccess } from "../../util/user.util";
import { GroupContext } from "./GroupContextProvider";
interface UserContextProviderProps {
interface UsersContextProviderProps {
directAccessUsers: I_User[];
setDirectAccessUsers: (value: I_User[]) => void;
inheritedAccessUsers: UserWithInheritedAccess[];
setInheritedAccessUsers: (value: UserWithInheritedAccess[]) => void;
availableUsers: I_User[];
setAvailableUsers: (value: I_User[]) => void;
getAvailableUsers: () => I_User[];
getAvailableUsersForResource: () => I_User[];
fetchUsersWithInheritedAccessForResource: () => Promise<void>;
fetchUsersWithDirectAccessForResource: () => Promise<void>;
}
export const UserContext = createContext<UserContextProviderProps>({
export const UsersContext = createContext<UsersContextProviderProps>({
directAccessUsers: [],
setDirectAccessUsers: () => {},
inheritedAccessUsers: [],
setInheritedAccessUsers: () => {},
availableUsers: [],
setAvailableUsers: () => {},
getAvailableUsers: () => {
getAvailableUsersForResource: () => {
return [];
},
fetchUsersWithInheritedAccessForResource: () => {
@ -41,7 +41,7 @@ export const UserContext = createContext<UserContextProviderProps>({
},
});
const UserContextProvider = ({ children }: { children: ReactNode }) => {
const UsersContextProvider = ({ children }: { children: ReactNode }) => {
const [directAccessUsers, setDirectAccessUsers] = useState<I_User[]>([]);
const [inheritedAccessUsers, setInheritedAccessUsers] = useState<
UserWithInheritedAccess[]
@ -88,7 +88,7 @@ const UserContextProvider = ({ children }: { children: ReactNode }) => {
}, []);
return (
<UserContext.Provider
<UsersContext.Provider
value={{
directAccessUsers,
setDirectAccessUsers,
@ -96,14 +96,14 @@ const UserContextProvider = ({ children }: { children: ReactNode }) => {
setInheritedAccessUsers,
availableUsers,
setAvailableUsers,
getAvailableUsers: getAvailableUsersForResource,
getAvailableUsersForResource,
fetchUsersWithInheritedAccessForResource,
fetchUsersWithDirectAccessForResource,
}}
>
{children}
</UserContext.Provider>
</UsersContext.Provider>
);
};
export default UserContextProvider;
export default UsersContextProvider;

View File

@ -1,5 +1,5 @@
import { Grid } from "@mui/material";
import UserContextProvider from "../../Context/UserContextProvider";
import UsersContextProvider from "../../Context/UsersContextProvider";
import AvailableUserDialog from "./AvailableUserDialog";
import InnovenergyList from "./InnovenergyList";
import UsersWithDirectAccess from "./UsersWithDirectAccess";
@ -7,7 +7,7 @@ import UsersWithInheritedAccess from "./UsersWithInheritedAccess";
const AccessManagement = () => {
return (
<UserContextProvider>
<UsersContextProvider>
<Grid container sx={{ mt: 1 }}>
<Grid item xs={6}>
<AvailableUserDialog />
@ -17,7 +17,7 @@ const AccessManagement = () => {
</InnovenergyList>
</Grid>
</Grid>
</UserContextProvider>
</UsersContextProvider>
);
};

View File

@ -1,6 +1,5 @@
import {
Autocomplete,
Button,
Dialog,
DialogContent,
DialogTitle,
@ -13,7 +12,8 @@ import { useParams } from "react-router-dom";
import axiosConfig from "../../../config/axiosConfig";
import { I_User } from "../../../util/user.util";
import { GroupContext } from "../../Context/GroupContextProvider";
import { UserContext } from "../../Context/UserContextProvider";
import { UsersContext } from "../../Context/UsersContextProvider";
import InnovenergyButton from "../../Layout/InnovenergyButton";
const AvailableUserDialog = () => {
const [selectedUsers, setSelectedUsers] = useState<I_User[]>([]);
@ -22,10 +22,10 @@ const AvailableUserDialog = () => {
const { currentType } = useContext(GroupContext);
const { id } = useParams();
const {
getAvailableUsers,
getAvailableUsersForResource,
fetchUsersWithDirectAccessForResource,
fetchUsersWithInheritedAccessForResource,
} = useContext(UserContext);
} = useContext(UsersContext);
const handleGrant = () => {
selectedUsers.forEach((user) => {
@ -63,7 +63,7 @@ const AvailableUserDialog = () => {
sx={{ width: "500px" }}
multiple
id="tags-standard"
options={getAvailableUsers()}
options={getAvailableUsersForResource()}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
@ -77,19 +77,18 @@ const AvailableUserDialog = () => {
/>
</DialogContent>
<DialogActions>
<Button
variant="outlined"
<InnovenergyButton
type="submit"
sx={{ height: 40, ml: 2 }}
onClick={handleGrant}
>
<FormattedMessage id="grantAccess" defaultMessage="Grant access" />
</Button>
</InnovenergyButton>
</DialogActions>
</Dialog>
<Button variant="outlined" type="submit" onClick={() => setOpen(true)}>
<InnovenergyButton type="submit" onClick={() => setOpen(true)}>
<FormattedMessage id="grantAccess" defaultMessage="Grant access" />
</Button>
</InnovenergyButton>
</>
);
};

View File

@ -10,13 +10,13 @@ import { Fragment, useContext, useEffect } from "react";
import axiosConfig from "../../../config/axiosConfig";
import PersonRemoveIcon from "@mui/icons-material/PersonRemove";
import PersonIcon from "@mui/icons-material/Person";
import { UserContext } from "../../Context/UserContextProvider";
import { UsersContext } from "../../Context/UsersContextProvider";
import { useParams } from "react-router-dom";
import { GroupContext } from "../../Context/GroupContextProvider";
const UsersWithDirectAccess = () => {
const { fetchUsersWithDirectAccessForResource, directAccessUsers } =
useContext(UserContext);
useContext(UsersContext);
const { currentType } = useContext(GroupContext);
const { id } = useParams();

View File

@ -13,11 +13,11 @@ import {
} from "../../../util/user.util";
import routes from "../../../routes.json";
import PersonIcon from "@mui/icons-material/Person";
import { UserContext } from "../../Context/UserContextProvider";
import { UsersContext } from "../../Context/UsersContextProvider";
const UsersWithInheritedAccess = () => {
const { fetchUsersWithInheritedAccessForResource, inheritedAccessUsers } =
useContext(UserContext);
useContext(UsersContext);
useEffect(() => {
fetchUsersWithInheritedAccessForResource();

View File

@ -1,9 +1,11 @@
import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material";
import { Dialog, DialogContent, DialogTitle, IconButton } from "@mui/material";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_Folder } from "../../util/types";
import FolderForm from "./FolderForm";
import CloseIcon from "@mui/icons-material/Close";
import InnovenergyButton from "../Layout/InnovenergyButton";
interface AddNewDialogProps {
values: I_Folder;
@ -13,17 +15,22 @@ const AddNewDialog = (props: AddNewDialogProps) => {
const [open, setOpen] = useState(false);
const handleSubmit = (data: I_Folder, childData: Partial<I_Folder>) => {
return axiosConfig.post("/CreateFolder", {
return axiosConfig
.post("/CreateFolder", {
...childData,
parentId: data.id,
})
.then((res) => {
setOpen(false);
return res;
});
};
return (
<>
<Button variant="outlined" sx={{ ml: 2 }} onClick={() => setOpen(true)}>
<InnovenergyButton sx={{ ml: 2 }} onClick={() => setOpen(true)}>
<FormattedMessage id="addNewChild" defaultMessage="Add new child" />
</Button>
</InnovenergyButton>
<Dialog
onClose={() => setOpen(false)}
aria-labelledby="customized-dialog-title"
@ -33,8 +40,25 @@ const AddNewDialog = (props: AddNewDialogProps) => {
maxHeight: 500,
}}
scroll="paper"
fullWidth
maxWidth="sm"
>
<DialogTitle>Create new folder</DialogTitle>
<DialogTitle>
Create new folder
<IconButton
edge="start"
color="inherit"
onClick={() => setOpen(false)}
aria-label="close"
sx={{
position: "absolute",
right: 8,
top: 8,
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<FolderForm values={props.values} handleSubmit={handleSubmit} />
</DialogContent>

View File

@ -1,4 +1,4 @@
import { Button, CircularProgress, Grid } from "@mui/material";
import { CircularProgress, Grid } from "@mui/material";
import { AxiosResponse } from "axios";
import { useFormik } from "formik";
import { ReactNode, useContext, useState } from "react";
@ -6,6 +6,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import { I_Folder } from "../../util/types";
import { GroupContext } from "../Context/GroupContextProvider";
import InnovenergySnackbar from "../InnovenergySnackbar";
import InnovenergyButton from "../Layout/InnovenergyButton";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
interface I_CustomerFormProps {
@ -28,8 +29,9 @@ const FolderForm = (props: I_CustomerFormProps) => {
const formik = useFormik({
initialValues: {
name: values.name,
information: values.information,
name: additionalButtons && additionalButtons.length ? values.name : "",
information:
additionalButtons && additionalButtons.length ? values.information : "",
},
onSubmit: (formikValues) => {
setLoading(true);
@ -73,9 +75,9 @@ const FolderForm = (props: I_CustomerFormProps) => {
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
{loading && <CircularProgress />}
{additionalButtons && additionalButtons.map((button) => button)}
<Button variant="outlined" type="submit" sx={{ ml: 2 }}>
<InnovenergyButton type="submit" sx={{ ml: 2 }}>
<FormattedMessage id="submit" defaultMessage="Submit" />
</Button>
</InnovenergyButton>
</Grid>
<InnovenergySnackbar
error={error}

View File

@ -9,15 +9,17 @@ import { AntTabs, StyledTab } from "../../util/installation.util";
const GroupTabs = () => {
const routeMatch = useRouteMatch([
routes.groups + routes.folder + ":id",
routes.groups + routes.manageAccess + ":id",
routes.groups + routes.installation + ":id",
routes.installations + routes.tree + routes.folder + ":id",
routes.installations + routes.tree + routes.manageAccess + ":id",
routes.installations + routes.tree + routes.installation + ":id",
]);
const id = routeMatch?.params?.id;
const intl = useIntl();
const { currentType } = useContext(GroupContext);
console.log(routeMatch);
if (id) {
return (
<Box sx={{ width: "100%" }}>
@ -32,7 +34,9 @@ const GroupTabs = () => {
id: "folder",
defaultMessage: "Folder",
})}
value={routes.groups + routes.folder + ":id"}
value={
routes.installations + routes.tree + routes.folder + ":id"
}
component={Link}
to={routes.folder + id}
/>
@ -42,7 +46,12 @@ const GroupTabs = () => {
id: "installation",
defaultMessage: "Installation",
})}
value={routes.groups + routes.installation + ":id"}
value={
routes.installations +
routes.tree +
routes.installation +
":id"
}
component={Link}
to={routes.installation + id}
/>
@ -53,7 +62,9 @@ const GroupTabs = () => {
id: "manageAccess",
defaultMessage: "Manage access",
})}
value={routes.groups + routes.manageAccess + ":id"}
value={
routes.installations + routes.tree + routes.manageAccess + ":id"
}
component={Link}
to={routes.manageAccess + id}
/>

View File

@ -1,16 +1,12 @@
import { Grid } from "@mui/material";
import { Container } from "@mui/system";
import { Routes, Route } from "react-router";
import routes from "../../routes.json";
import Folder from "./Folder";
import GroupTabs from "./GroupTabs";
import GroupContextProvider from "../Context/GroupContextProvider";
import GroupTree from "./Tree/GroupTree";
import NavigationButtons from "../Layout/NavigationButtons";
import AccessManagement from "./AccessManagement/AccessManagement";
import { I_Installation } from "../../util/types";
import InstallationForm from "../Installations/InstallationForm";
import Detail from "../Layout/Detail";
import Installation from "../Installations/Installation";
const Groups = () => {
return (
@ -29,12 +25,7 @@ const Groups = () => {
/>
<Route
path={routes.installation + ":id"}
element={
<Detail<I_Installation>
route="/GetInstallationById?id="
formComponent={InstallationForm}
/>
}
element={<Installation hasMoveButton />}
/>
</Routes>
</Grid>

View File

@ -1,10 +1,10 @@
import { Button, Grid, InputLabel } from "@mui/material";
import { Grid, InputLabel } from "@mui/material";
import { useContext, useState } from "react";
import { FormattedMessage } from "react-intl";
import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig";
import { GroupContext } from "../Context/GroupContextProvider";
import MoveTree from "./Tree/MoveTree";
import InnovenergyButton from "../Layout/InnovenergyButton";
interface LocationFormProps {
parentId: number;
@ -39,13 +39,9 @@ const LocationForm = (props: LocationFormProps) => {
</Grid>
<Grid item xs={9} display="inline"></Grid>
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<Button
variant="outlined"
sx={{ height: 40, ml: 2 }}
onClick={handleMove}
>
<InnovenergyButton sx={{ height: 40, ml: 2 }} onClick={handleMove}>
<FormattedMessage id="move" defaultMessage="Move" />
</Button>
</InnovenergyButton>
</Grid>
</Grid>
);

View File

@ -19,14 +19,6 @@ const GroupTree = () => {
fetchData();
}, [fetchData]);
/* useEffect(() => {
if (id) {
setSelected(
currentType === "Folder" ? "Folder" + id : "Installation" + id
);
}
}, [id, currentType]); */
const getNodes = (element: I_Folder | I_Installation): null | ReactNode => {
if (instanceOfFolder(element)) {
return element.children ? renderTree(element.children) : null;
@ -55,6 +47,9 @@ const GroupTree = () => {
nodeId={element.type + element.id}
label={element.name}
onClick={() => setCurrentType(element.type)}
sx={{
".MuiTreeItem-content": { paddingY: "12px" },
}}
>
{getNodes(element)}
</TreeItem>
@ -75,7 +70,12 @@ const GroupTree = () => {
aria-label="rich object"
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ height: 300, flexGrow: 1, maxWidth: 400 }}
sx={{
height: 480,
flexGrow: 1,
overflow: "auto",
".MuiTreeView-root": { overflowX: "hidden" },
}}
expanded={openNodes}
onNodeToggle={(e, ids) => {
setOpenNodes(ids);

View File

@ -1,4 +1,4 @@
import { Button, DialogActions, Dialog, DialogTitle } from "@mui/material";
import { DialogActions, Dialog, DialogTitle } from "@mui/material";
import DialogContent from "@mui/material/DialogContent";
import { useContext, useState } from "react";
import { FormattedMessage } from "react-intl";
@ -6,6 +6,7 @@ import { useParams } from "react-router-dom";
import axiosConfig from "../../../config/axiosConfig";
import { I_Folder } from "../../../util/types";
import { GroupContext } from "../../Context/GroupContextProvider";
import InnovenergyButton from "../../Layout/InnovenergyButton";
import MoveTree from "./MoveTree";
interface MoveDialogProps {
@ -37,9 +38,9 @@ const MoveDialog = (props: MoveDialogProps) => {
return (
<div>
<Button variant="outlined" onClick={() => setOpen(true)}>
<InnovenergyButton onClick={() => setOpen(true)} sx={{ mr: 1 }}>
<FormattedMessage id="move" defaultMessage="Move" />
</Button>
</InnovenergyButton>
<Dialog
onClose={() => setOpen(false)}
aria-labelledby="customized-dialog-title"
@ -58,9 +59,7 @@ const MoveDialog = (props: MoveDialogProps) => {
/>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleMove}>
Move
</Button>
<InnovenergyButton onClick={handleMove}>Move</InnovenergyButton>
</DialogActions>
</Dialog>
</div>

View File

@ -0,0 +1,60 @@
import { Alert, Box, CircularProgress } from "@mui/material";
import { AxiosError } from "axios";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types";
import InstallationForm from "./InstallationForm";
interface I_InstallationProps {
hasMoveButton?: boolean;
}
const Installation = (props: I_InstallationProps) => {
const { id } = useParams();
const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
useEffect(() => {
setLoading(true);
axiosConfig
.get("/GetInstallationById?id=" + id)
.then((res) => {
setValues(res.data);
setLoading(false);
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}, [id]);
if (values && values.id && values.id.toString() === id) {
return (
<Box sx={{ py: 3 }}>
<InstallationForm
values={values}
id={id}
hasMoveButton={props.hasMoveButton}
/>
</Box>
);
} else if (loading) {
return (
<Box
sx={{ width: 1 / 2, justifyContent: "center", display: "flex", mt: 10 }}
>
<CircularProgress sx={{ m: 2 }} />
</Box>
);
} else if (error) {
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}
</Alert>
);
}
return null;
};
export default Installation;

View File

@ -1,21 +1,25 @@
import { Alert, Button, Grid, Snackbar } from "@mui/material";
import { Alert, Grid, Snackbar } from "@mui/material";
import { useFormik } from "formik";
import { useState } from "react";
import { useContext, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types";
import { InstallationContext } from "../Context/InstallationContextProvider";
import MoveDialog from "../Groups/Tree/MoveDialog";
import InnovenergyButton from "../Layout/InnovenergyButton";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
interface I_InstallationFormProps {
values: I_Installation;
id: string | undefined;
hasMoveButton?: boolean;
}
const InstallationForm = (props: I_InstallationFormProps) => {
const { values, id } = props;
const { values, id, hasMoveButton } = props;
const [open, setOpen] = useState(false);
const intl = useIntl();
const { fetchData } = useContext(InstallationContext);
const formik = useFormik({
initialValues: {
@ -33,6 +37,7 @@ const InstallationForm = (props: I_InstallationFormProps) => {
})
.then(() => {
setOpen(true);
fetchData();
});
},
});
@ -94,10 +99,10 @@ const InstallationForm = (props: I_InstallationFormProps) => {
handleChange={formik.handleChange}
/>
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<MoveDialog values={values} />
<Button variant="outlined" type="submit">
{hasMoveButton && <MoveDialog values={values} />}
<InnovenergyButton type="submit">
<FormattedMessage id="applyChanges" defaultMessage="Apply changes" />
</Button>
</InnovenergyButton>
</Grid>
<Snackbar
open={open}

View File

@ -41,9 +41,9 @@ const InstallationList = (props: InstallationListProps) => {
const filteredData = filterData(props.searchQuery, data);
const routeMatch = useRouteMatch([
routes.installations + routes.installation + ":id",
routes.installations + routes.alarms + ":id",
routes.installations + routes.log + ":id",
routes.installations + routes.list + routes.installation + ":id",
routes.installations + routes.list + routes.alarms + ":id",
routes.installations + routes.list + routes.log + ":id",
]);
useEffect(() => {
@ -95,7 +95,6 @@ const InstallationList = (props: InstallationListProps) => {
</List>
);
} else if (error) {
console.log(error);
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}

View File

@ -1,12 +1,9 @@
import * as React from "react";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import { Link, Routes } from "react-router-dom";
import { Link } from "react-router-dom";
import routes from "../../routes.json";
import useRouteMatch from "../../hooks/useRouteMatch";
import { useIntl } from "react-intl";
import { styled } from "@mui/material";
import { AntTabs, StyledTab } from "../../util/installation.util";
const InstallationTabs = () => {
@ -25,7 +22,7 @@ const InstallationTabs = () => {
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<AntTabs
value={routeMatch?.pattern?.path ?? routes.installation + ":id"}
aria-label="basic tabs example"
aria-label="installation tabs"
>
<StyledTab
label={intl.formatMessage({

View File

@ -1,35 +1,27 @@
import { Grid } from "@mui/material";
import { Container } from "@mui/system";
import { Routes, Route } from "react-router";
import NavigationButtons from "../Layout/NavigationButtons";
import Sidebar from "../Layout/Sidebar";
import Alarms from "./Alarms";
import InstallationTabs from "./InstallationTabs";
import Log from "./Log";
import routes from "../../routes.json";
import InstallationContextProvider from "../Context/InstallationContextProvider";
import Detail from "../Layout/Detail";
import { I_Installation } from "../../util/types";
import InstallationForm from "./InstallationForm";
import SearchSidebar from "../Layout/Search";
import InstallationList from "./InstallationList";
import Installation from "./Installation";
const Installations = () => {
return (
<InstallationContextProvider>
<Grid container spacing={2}>
<Grid item xs={3}>
<Sidebar />
<SearchSidebar listComponent={InstallationList} />
</Grid>
<Grid item xs={9}>
<InstallationTabs />
<Routes>
<Route
path={routes.installation + ":id"}
element={
<Detail<I_Installation>
route="/GetInstallationById?id="
formComponent={InstallationForm}
/>
}
element={<Installation />}
index
/>
<Route path={routes.alarms + ":id"} element={<Alarms />} />

View File

@ -149,7 +149,6 @@ const Log = () => {
return previous;
}, {});
console.log(flattenObject(foo.Devices[0]));
return <div>log</div>;
};

View File

@ -6,14 +6,16 @@ import axiosConfig from "../../config/axiosConfig";
export interface I_FormProps<T> {
values: T;
id: string;
hasMoveButton?: boolean;
}
interface I_DetailProps<T> {
formComponent: FC<I_FormProps<T>>;
route: string;
hasMoveButton?: boolean;
}
const Detail = <T extends { id: number }>(props: I_DetailProps<T>) => {
const { id } = useParams();
const { formComponent: FormComponent, route } = props;
const { formComponent: FormComponent, route, hasMoveButton } = props;
const [values, setValues] = useState<T>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
@ -33,7 +35,9 @@ const Detail = <T extends { id: number }>(props: I_DetailProps<T>) => {
}, [id, route]);
if (values && values.id && values.id.toString() === id) {
return <FormComponent values={values} id={id} />;
return (
<FormComponent values={values} id={id} hasMoveButton={hasMoveButton} />
);
} else if (loading) {
return (
<Box

View File

@ -0,0 +1,24 @@
import { Button, SxProps, Theme } from "@mui/material";
import { ReactNode } from "react";
interface I_InnovenergyButtonProps {
children?: ReactNode;
type?: "button" | "submit" | "reset" | undefined;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
sx?: SxProps<Theme> | undefined;
}
const InnovenergyButton = (props: I_InnovenergyButtonProps) => {
return (
<Button
variant="contained"
type={props.type}
onClick={props.onClick}
sx={props.sx}
>
{props.children}
</Button>
);
};
export default InnovenergyButton;

View File

@ -1,7 +1,7 @@
import { Button } from "@mui/material";
import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig";
import InnovenergyButton from "./InnovenergyButton";
interface LogoutButtonProps {
removeToken: () => void;
@ -10,8 +10,7 @@ const LogoutButton = (props: LogoutButtonProps) => {
const navigate = useNavigate();
return (
<Button
variant="outlined"
<InnovenergyButton
onClick={() => {
axiosConfig.post("/Logout").then(() => {
navigate("/");
@ -21,7 +20,7 @@ const LogoutButton = (props: LogoutButtonProps) => {
sx={{ mx: 1 }}
>
<FormattedMessage id="logout" defaultMessage="Logout" />
</Button>
</InnovenergyButton>
);
};

View File

@ -3,7 +3,7 @@ import useRouteMatch from "../../hooks/useRouteMatch";
import routes from "../../routes.json";
import ListIcon from "@mui/icons-material/List";
import AccountTreeIcon from "@mui/icons-material/AccountTree";
import { AntTabs, StyledTab } from "../../util/installation.util";
import { ToggleButtonGroup, ToggleButton } from "@mui/material";
const ModeButtons = () => {
const routeMatch = useRouteMatch([
@ -13,7 +13,7 @@ const ModeButtons = () => {
return (
<>
<AntTabs
{/* <AntTabs
value={routeMatch?.pattern?.path ?? routes.installation + ":id"}
aria-label="basic tabs example"
>
@ -29,8 +29,8 @@ const ModeButtons = () => {
component={Link}
to={routes.tree}
/>
</AntTabs>
{/* <ToggleButtonGroup
</AntTabs> */}
<ToggleButtonGroup
color="primary"
value={routeMatch?.pattern?.path}
exclusive
@ -51,7 +51,7 @@ const ModeButtons = () => {
>
<AccountTreeIcon />
</ToggleButton>
</ToggleButtonGroup> */}
</ToggleButtonGroup>
</>
);
};

View File

@ -1,8 +1,8 @@
import { ToggleButton, ToggleButtonGroup } from "@mui/material";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import useRouteMatch from "../../hooks/useRouteMatch";
import routes from "../../routes.json";
import { AntTabs, StyledTab } from "../../util/installation.util";
const NavigationButtons = () => {
const routeMatch = useRouteMatch([
@ -11,7 +11,8 @@ const NavigationButtons = () => {
]);
return (
<ToggleButtonGroup
<>
{/* <ToggleButtonGroup
color="primary"
value={routeMatch?.pattern?.path}
exclusive
@ -32,7 +33,30 @@ const NavigationButtons = () => {
>
<FormattedMessage id="users" defaultMessage="Users" />
</ToggleButton>
</ToggleButtonGroup>
</ToggleButtonGroup> */}
<AntTabs
value={routeMatch?.pattern?.path}
aria-label="basic tabs example"
>
<StyledTab
value={routes.installations + "*"}
component={Link}
to={routes.installations + routes.list}
label={
<FormattedMessage
id="installations"
defaultMessage="Installations"
/>
}
/>
<StyledTab
value={routes.users + "*"}
component={Link}
to={routes.users}
label={<FormattedMessage id="users" defaultMessage="Users" />}
/>
</AntTabs>
</>
);
};

View File

@ -1,9 +1,12 @@
import { TextField } from "@mui/material";
import { useState } from "react";
import { FC, useState } from "react";
import { useIntl } from "react-intl";
import InstallationList from "../Installations/InstallationList";
const Sidebar = () => {
interface SearchSidebarProps {
listComponent: FC<{ searchQuery: string }>;
}
const SearchSidebar = (props: SearchSidebarProps) => {
const { listComponent: ListComponent } = props;
const [searchQuery, setSearchQuery] = useState("");
const intl = useIntl();
@ -20,10 +23,9 @@ const Sidebar = () => {
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<InstallationList searchQuery={searchQuery} />
<ListComponent searchQuery={searchQuery} />
</>
);
};
export default Sidebar;
export default SearchSidebar;

View File

@ -0,0 +1,61 @@
import { Dialog, DialogTitle, IconButton, DialogContent } from "@mui/material";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_User } from "../../util/user.util";
import InnovenergyButton from "../Layout/InnovenergyButton";
import CloseIcon from "@mui/icons-material/Close";
import UserForm from "./UserForm";
const AddUser = () => {
const [open, setOpen] = useState(false);
const handleSubmit = (data: Partial<I_User>) => {
return axiosConfig.post("/CreateUser", data).then((res) => {
setOpen(false);
return res;
});
};
return (
<>
<InnovenergyButton sx={{ my: 1 }} onClick={() => setOpen(true)}>
<FormattedMessage id="addNewChild" defaultMessage="Add user" />
</InnovenergyButton>
<Dialog
onClose={() => setOpen(false)}
aria-labelledby="customized-dialog-title"
open={open}
sx={{
".MuiDialogContent-root": { overflowX: "hidden" },
maxHeight: 500,
}}
scroll="paper"
fullWidth
maxWidth="sm"
>
<DialogTitle>
Create new user
<IconButton
edge="start"
color="inherit"
onClick={() => setOpen(false)}
aria-label="close"
sx={{
position: "absolute",
right: 8,
top: 8,
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<UserForm handleSubmit={handleSubmit} />
</DialogContent>
</Dialog>
</>
);
};
export default AddUser;

View File

@ -0,0 +1,59 @@
import { Box, CircularProgress, Alert } from "@mui/material";
import { AxiosError } from "axios";
import { useState, useEffect, FC } from "react";
import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig";
import { I_User } from "../../util/user.util";
import UserForm from "./UserForm";
interface I_DetailProps<T> {
hasMoveButton?: boolean;
}
const Detail = <T extends { id: number }>(props: I_DetailProps<T>) => {
const { id } = useParams();
const [values, setValues] = useState<I_User>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
useEffect(() => {
setLoading(true);
axiosConfig
.get("/GetUserById?id=" + id)
.then((res) => {
setValues(res.data);
setLoading(false);
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}, [id]);
const handleUpdate = (formikValues: Partial<I_User>) => {
return axiosConfig.put("/UpdateUser", {
...formikValues,
id: id,
});
};
if (values && values.id && values.id.toString() === id) {
return <UserForm values={values} handleSubmit={handleUpdate} />;
} else if (loading) {
return (
<Box
sx={{ width: 1 / 2, justifyContent: "center", display: "flex", mt: 10 }}
>
<CircularProgress sx={{ m: 2 }} />
</Box>
);
} else if (error) {
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}
</Alert>
);
}
return null;
};
export default Detail;

View File

@ -1,33 +1,29 @@
import { Alert, Button, Grid, Snackbar } from "@mui/material";
import { Alert, Grid, Snackbar } from "@mui/material";
import { AxiosResponse } from "axios";
import { useFormik } from "formik";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_User } from "../../util/user.util";
import InnovenergyButton from "../Layout/InnovenergyButton";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
interface I_UserFormProps {
values: I_User;
id: string;
handleSubmit: (formikValues: Partial<I_User>) => Promise<AxiosResponse>;
values?: I_User;
}
const UserForm = (props: I_UserFormProps) => {
const { values, id } = props;
const { values, handleSubmit } = props;
const [open, setOpen] = useState(false);
const intl = useIntl();
const formik = useFormik({
initialValues: {
email: values.email,
information: values.information,
email: values ? values.email : "",
information: values ? values.information : "",
},
onSubmit: (formikValues) => {
axiosConfig
.put("/UpdateUser", {
...formikValues,
id: id,
})
.then(() => {
handleSubmit(formikValues).then(() => {
setOpen(true);
});
},
@ -60,9 +56,9 @@ const UserForm = (props: I_UserFormProps) => {
handleChange={formik.handleChange}
/>
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<Button variant="outlined" type="submit">
<InnovenergyButton type="submit">
<FormattedMessage id="applyChanges" defaultMessage="Apply changes" />
</Button>
</InnovenergyButton>
</Grid>
<Snackbar
open={open}

View File

@ -6,8 +6,8 @@ import { Link } from "react-router-dom";
import useRouteMatch from "../../hooks/useRouteMatch";
import routes from "../../routes.json";
import { Fragment, useContext } from "react";
import { I_Installation } from "../../util/types";
import { UserContext } from "../Context/UserContextProvider";
import { UsersContext } from "../Context/UsersContextProvider";
import { I_User } from "../../util/user.util";
const getPathWithoutId = (path?: string) => {
if (path) {
@ -17,26 +17,26 @@ const getPathWithoutId = (path?: string) => {
return routes.user;
};
const filterData = (
searchQuery: string,
data: I_Installation[] | undefined
) => {
const filterData = (searchQuery: string, data: I_User[] | undefined) => {
if (data) {
return data.filter(
(installation) =>
installation.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
installation.location.toLowerCase().includes(searchQuery.toLowerCase())
return data.filter((installation) =>
installation.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}
return data;
};
const UserList = () => {
const { availableUsers } = useContext(UserContext);
interface UserListProps {
searchQuery: string;
}
const UserList = (props: UserListProps) => {
const { availableUsers } = useContext(UsersContext);
const filteredData = filterData(props.searchQuery, availableUsers);
const routeMatch = useRouteMatch([routes.users + routes.user + ":id"]);
if (availableUsers && availableUsers.length) {
if (filteredData && filteredData.length) {
return (
<List
sx={{
@ -51,7 +51,7 @@ const UserList = () => {
component="nav"
aria-labelledby="nested-list-subheader"
>
{availableUsers.map((user) => {
{filteredData.map((user) => {
return (
<Fragment key={user.id}>
<Link

View File

@ -1,11 +1,10 @@
import * as React from "react";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import { Link } from "react-router-dom";
import routes from "../../routes.json";
import useRouteMatch from "../../hooks/useRouteMatch";
import { useIntl } from "react-intl";
import { AntTabs, StyledTab } from "../../util/installation.util";
const UserTabs = () => {
const routeMatch = useRouteMatch([routes.users + routes.user + ":id"]);
@ -17,11 +16,11 @@ const UserTabs = () => {
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
<AntTabs
value={routeMatch?.pattern?.path ?? routes.user + ":id"}
aria-label="basic tabs example"
aria-label="user tabs"
>
<Tab
<StyledTab
label={intl.formatMessage({
id: "user",
defaultMessage: "User",
@ -30,7 +29,7 @@ const UserTabs = () => {
component={Link}
to={routes.user + id}
/>
</Tabs>
</AntTabs>
</Box>
</Box>
);

View File

@ -1,38 +1,29 @@
import { Grid } from "@mui/material";
import { Container } from "@mui/system";
import { Routes, Route } from "react-router";
import routes from "../../routes.json";
import { I_User } from "../../util/user.util";
import UserContextProvider from "../Context/UserContextProvider";
import Detail from "../Layout/Detail";
import UserForm from "./UserForm";
import UsersContextProvider from "../Context/UsersContextProvider";
import SearchSidebar from "../Layout/Search";
import AddUser from "./AddUser";
import User from "./User";
import UserList from "./UserList";
import UserTabs from "./UserTabs";
const Users = () => {
return (
<UserContextProvider>
<Grid container spacing={2}>
<UsersContextProvider>
<Grid container>
<Grid item xs={3}>
<UserList />
<AddUser />
<SearchSidebar listComponent={UserList} />
</Grid>
<Grid item xs={9}>
<UserTabs />
<Routes>
<Route
path={routes.user + ":id"}
element={
<Detail<I_User>
route="/GetUserById?id="
formComponent={UserForm}
/>
}
index
/>
<Route path={routes.user + ":id"} element={<User />} />
</Routes>
</Grid>
</Grid>
</UserContextProvider>
</UsersContextProvider>
);
};

View File

@ -17,6 +17,6 @@
"updatedSuccessfully": "Updated successfully",
"groups": "Groups",
"group": "Group",
"folder": "folder",
"folder": "Folder",
"updateFolderErrorMessage": "Couldn't update folder, an error occured"
}

View File

@ -7,7 +7,6 @@ export const StyledTab = styled((props: any) => (
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(15),
marginRight: theme.spacing(1),
color: "#0d6efd",
background: "0 0",
border: "1px solid transparent",
borderTopLeftRadius: "0.25rem",
@ -19,7 +18,7 @@ export const StyledTab = styled((props: any) => (
color: "#495057",
backgroundColor: "#fff",
borderColor: "#dee2e6 #dee2e6 #fff",
marginBottom: "-2px",
marginBottom: "-3px",
},
"&.Mui-focusVisible": {
backgroundColor: "rgba(100, 95, 228, 0.32)",
@ -28,7 +27,6 @@ export const StyledTab = styled((props: any) => (
export const AntTabs = styled(Tabs)({
borderBottom: "1px solid #dee2e6",
overflow: "visible!important",
"& div.MuiTabs-scroller": {
overflow: "visible!important",

View File

@ -1,10 +1,10 @@
// TODO add if required or not
export interface I_Installation {
type: string;
title: string;
status: number;
detail: string;
instance: string;
title?: string;
status?: number;
detail?: string;
instance?: string;
location: string;
region: string;
country: string;
@ -16,6 +16,7 @@ export interface I_Installation {
name: string;
information: string;
parentId: number;
s3Url: string;
}
export interface I_Folder {