[WIP] style changes, work on error handling and user feedback

This commit is contained in:
Sina Blattmann 2023-02-23 16:37:14 +01:00
parent cba19ee1ed
commit 633c3de5bc
11 changed files with 331 additions and 270 deletions

View File

@ -1,10 +1,10 @@
import useToken from "./hooks/useToken"; import useToken from "./hooks/useToken";
import Login from "./Login"; import Login from "./Login";
import { BrowserRouter, Route, Routes } from "react-router-dom"; import { BrowserRouter, Route, Routes } from "react-router-dom";
import { Box, Grid, Divider } from "@mui/material"; import { Box, Grid, Divider, createTheme, ThemeProvider } from "@mui/material";
import NestedList from "./components/NestedList"; import InstallationList from "./components/InstallationList";
import BasicTable from "./components/Table"; import BasicTable from "./components/Table";
import BasicTabs from "./components/Tabs"; import InstallationTabs from "./components/InstallationTabs";
import Alarms from "./routes/Alarms"; import Alarms from "./routes/Alarms";
import InstallationDetail from "./routes/Installation"; import InstallationDetail from "./routes/Installation";
import Log from "./routes/Log"; import Log from "./routes/Log";
@ -13,11 +13,23 @@ import { IntlProvider } from "react-intl";
import { useState } from "react"; import { useState } from "react";
import en from "./lang/en.json"; import en from "./lang/en.json";
import de from "./lang/de.json"; import de from "./lang/de.json";
import LanguageSelect from "./components/LanguageSelect";
import LogoutButton from "./components/LogoutButton";
import NavigationButtons from "./components/NavigationButtons"; import NavigationButtons from "./components/NavigationButtons";
import UserList from "./components/UserTree";
const App = () => { const App = () => {
const { token, setToken, removeToken } = useToken(); const { token, setToken, removeToken } = useToken();
const [language, setLanguage] = useState("en"); const [language, setLanguage] = useState("en");
const [currentView, setCurrentView] = useState("installations");
const theme = createTheme({
palette: {
primary: {
main: "#F59100",
},
},
});
const getTranslations = () => { const getTranslations = () => {
if (language === "de") { if (language === "de") {
@ -32,46 +44,62 @@ const App = () => {
return ( return (
<BrowserRouter> <BrowserRouter>
<IntlProvider <ThemeProvider theme={theme}>
messages={getTranslations()} <IntlProvider
locale={language} messages={getTranslations()}
defaultLocale="en" locale={language}
> defaultLocale="en"
<Box sx={{ padding: 2 }}> >
<Grid container spacing={2}> <Grid container justifyContent="flex-end" sx={{ px: 1, pt: 1 }}>
<Grid item xs={3}> <LanguageSelect language={language} setLanguage={setLanguage} />
<NavigationButtons /> <LogoutButton removeToken={removeToken} />
<NestedList />
</Grid>
<Grid
item
xs={1}
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Divider orientation="vertical" variant="middle" />
</Grid>
<Grid item xs={8}>
<BasicTabs
removeToken={removeToken}
language={language}
setLanguage={setLanguage}
/>
<Routes>
<Route
path={routes.installationWithId}
element={<InstallationDetail />}
/>
<Route path={routes.alarmsWithId} element={<Alarms />} />
<Route path={routes.usersWithId} element={<BasicTable />} />
<Route path={routes.logWithId} element={<Log />} />
</Routes>
</Grid>
</Grid> </Grid>
</Box> <Box sx={{ p: 1 }}>
</IntlProvider> <Grid container spacing={2}>
<Grid item xs={3}>
<NavigationButtons
currentView={currentView}
setCurrentView={setCurrentView}
/>
{currentView === "installations" ? (
<InstallationList />
) : (
<UserList />
)}
</Grid>
<Grid
item
xs={1}
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Divider orientation="vertical" variant="middle" />
</Grid>
<Grid item xs={8}>
{currentView === "installations" && (
<>
<InstallationTabs />
<Routes>
<Route
path={routes.installationWithId}
element={<InstallationDetail />}
/>
<Route path={routes.alarmsWithId} element={<Alarms />} />
<Route
path={routes.usersWithId}
element={<BasicTable />}
/>
<Route path={routes.logWithId} element={<Log />} />
</Routes>
</>
)}
</Grid>
</Grid>
</Box>
</IntlProvider>
</ThemeProvider>
</BrowserRouter> </BrowserRouter>
); );
}; };

View File

@ -1,4 +1,4 @@
import { Alert, Button, Snackbar } from "@mui/material"; import { Alert, Button, Grid, InputLabel, Snackbar } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useState } from "react"; import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -92,20 +92,25 @@ const CustomerForm = (props: I_CustomerFormProps) => {
value={formik.values.orderNumbers} value={formik.values.orderNumbers}
handleChange={formik.handleChange} handleChange={formik.handleChange}
/> />
<Button variant="outlined" type="submit"> <Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<FormattedMessage id="applyChanges" defaultMessage="Apply changes" /> <Button variant="outlined" type="submit">
</Button> <FormattedMessage id="applyChanges" defaultMessage="Apply changes" />
</Button>
</Grid>
<Snackbar <Snackbar
open={open} open={open}
anchorOrigin={{ anchorOrigin={{
vertical: "bottom", vertical: "top",
horizontal: "right", horizontal: "center",
}} }}
autoHideDuration={6000} autoHideDuration={6000}
onClose={handleClose} onClose={handleClose}
> >
<Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}> <Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
<FormattedMessage id="updatedSuccessfully" defaultMessage="Updated successfully" /> <FormattedMessage
id="updatedSuccessfully"
defaultMessage="Updated successfully"
/>
</Alert> </Alert>
</Snackbar> </Snackbar>
</form> </form>

View File

@ -1,4 +1,4 @@
import { TextField } from "@mui/material"; import { Grid, InputLabel, TextField } from "@mui/material";
interface I_InnovenergyTextfieldProps { interface I_InnovenergyTextfieldProps {
id: string; id: string;
@ -7,21 +7,31 @@ interface I_InnovenergyTextfieldProps {
name: string; name: string;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void; handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
type?: string; type?: string;
readOnly?: boolean;
} }
const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => { const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => {
return ( return (
<TextField <Grid container direction="row" alignItems="center" spacing={2}>
id={props.id} <Grid item xs={3}>
label={props.label} <InputLabel>{props.label}</InputLabel>
variant="outlined" </Grid>
name={props.name} <Grid item xs={9}>
type={props.type} <TextField
fullWidth id={props.id}
sx={{ my: 0.5 }} variant="outlined"
value={props.value || ""} name={props.name}
onChange={props.handleChange} type={props.type}
/> fullWidth
sx={{ my: 0.5 }}
value={props.value || ""}
onChange={props.handleChange}
InputProps={{
readOnly: props.readOnly,
}}
/>
</Grid>
</Grid>
); );
}; };

View File

@ -1,95 +1,102 @@
import List from "@mui/material/List"; import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import { Divider, TextField } from "@mui/material"; import { Divider, TextField } 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, useEffect, useState } from "react";
import { I_Installation } from "../util/installation.util"; import { I_Installation } from "../util/installation.util";
import axiosConfig from "../config/axiosConfig"; import axiosConfig from "../config/axiosConfig";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
const getPathWithoutId = (path?: string) => { const getPathWithoutId = (path?: string) => {
if (path) { if (path) {
const splitString = path.split(":"); const splitString = path.split(":");
return splitString[0]; return splitString[0];
} }
return routes.installation; return routes.installation;
}; };
const filterData = ( const filterData = (
searchQuery: string, searchQuery: string,
data: I_Installation[] | undefined data: I_Installation[] | undefined
) => { ) => {
if (data) { if (data) {
return data.filter( return data.filter(
(installation) => (installation) =>
installation.name.toLowerCase().includes(searchQuery.toLowerCase()) || installation.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
installation.location.toLowerCase().includes(searchQuery.toLowerCase()) installation.location.toLowerCase().includes(searchQuery.toLowerCase())
); );
} }
return data; return data;
}; };
const NestedList = () => { const InstallationList = () => {
const [data, setData] = useState<I_Installation[]>(); const [data, setData] = useState<I_Installation[]>();
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const intl = useIntl(); const intl = useIntl();
const filteredData = filterData(searchQuery, data); const filteredData = filterData(searchQuery, data);
const routeMatch = useRouteMatch([ const routeMatch = useRouteMatch([
routes.installationWithId, routes.installationWithId,
routes.alarmsWithId, routes.alarmsWithId,
routes.usersWithId, routes.usersWithId,
routes.logWithId, routes.logWithId,
]); ]);
useEffect(() => { useEffect(() => {
console.log("useeffect"); axiosConfig.get("/GetAllInstallations", {}).then((res) => {
axiosConfig.get("/GetAllInstallations", {}).then((res) => { setData(res.data);
setData(res.data); });
}); }, []);
}, []);
return (
return ( <>
<> <TextField
<TextField id="outlined-search"
id="outlined-search" label={intl.formatMessage({
label={intl.formatMessage({ id: "search",
id: "search", defaultMessage: "Search",
defaultMessage: "Search", })}
})} type="search"
type="search" fullWidth
fullWidth value={searchQuery}
value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)}
onChange={(e) => setSearchQuery(e.target.value)} />
/> <List
<List sx={{
sx={{ width: "100%", bgcolor: "background.paper" }} width: "100%",
component="nav" bgcolor: "background.paper",
aria-labelledby="nested-list-subheader" position: "relative",
> overflow: "auto",
{filteredData?.map((installation) => ( maxHeight: 400,
<Fragment key={installation.id}> py: 0,
<Link mt: 1,
to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id} }}
style={{ textDecoration: "none", color: "black" }} component="nav"
> aria-labelledby="nested-list-subheader"
<ListItemButton >
selected={installation.id === Number(routeMatch?.params.id)} {filteredData?.map((installation) => (
> <Fragment key={installation.id}>
<ListItemText <Link
primary={installation.location + " | " + installation.name} to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id}
/> style={{ textDecoration: "none", color: "black" }}
</ListItemButton> >
</Link> <ListItemButton
<Divider /> selected={installation.id === Number(routeMatch?.params.id)}
</Fragment> >
))} <ListItemText
</List> primary={installation.location + " | " + installation.name}
</> />
); </ListItemButton>
}; </Link>
<Divider />
export default NestedList; </Fragment>
))}
</List>
</>
);
};
export default InstallationList;

View File

@ -0,0 +1,73 @@
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";
const InstallationTabs = () => {
const routeMatch = useRouteMatch([
routes.installationWithId,
routes.alarmsWithId,
routes.usersWithId,
routes.logWithId,
]);
const id = routeMatch?.params?.id;
const intl = useIntl();
if (id) {
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={routeMatch?.pattern?.path ?? routes.installationWithId}
aria-label="basic tabs example"
>
<Tab
label={intl.formatMessage({
id: "installation",
defaultMessage: "Installation",
})}
value={routes.installationWithId}
component={Link}
to={routes.installation + id}
/>
<Tab
label={intl.formatMessage({
id: "alarms",
defaultMessage: "Alarms",
})}
value={routes.alarmsWithId}
component={Link}
to={routes.alarms + id}
/>
<Tab
label={intl.formatMessage({
id: "users",
defaultMessage: "Users",
})}
value={routes.usersWithId}
component={Link}
to={routes.users + id}
/>
<Tab
label={intl.formatMessage({
id: "log",
defaultMessage: "Log",
})}
value={routes.logWithId}
component={Link}
to={routes.log + id}
/>
</Tabs>
</Box>
</Box>
);
}
return null;
};
export default InstallationTabs;

View File

@ -13,7 +13,6 @@ const LanguageSelect = (props: LanguageSelectProps) => {
value={props.language} value={props.language}
label="Age" label="Age"
onChange={(e) => props.setLanguage(e.target.value)} onChange={(e) => props.setLanguage(e.target.value)}
sx={{ ml: "auto" }}
> >
<MenuItem value="en"> <MenuItem value="en">
<FormattedMessage id="english" defaultMessage="English" /> <FormattedMessage id="english" defaultMessage="English" />

View File

@ -1,23 +1,36 @@
import { Button, ButtonGroup } from "@mui/material"; import { ToggleButton, ToggleButtonGroup } from "@mui/material";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
const NavigationButtons = () => { interface NavigationButtonsProps {
currentView: string;
setCurrentView: (value: string) => void;
}
const NavigationButtons = (props: NavigationButtonsProps) => {
const handleChange = (
event: React.MouseEvent<HTMLElement>,
newAlignment: string
) => {
props.setCurrentView(newAlignment);
};
return ( return (
<ButtonGroup <ToggleButtonGroup
variant="outlined" color="primary"
aria-label="outlined primary button group" value={props.currentView}
sx={{ paddingBottom: 3 }} exclusive
onChange={handleChange}
sx={{ mb: 1 }}
> >
<Button> <ToggleButton value="installations">
<FormattedMessage <FormattedMessage
id="allInstallations" id="allInstallations"
defaultMessage="All installations" defaultMessage="All installations"
/> />
</Button> </ToggleButton>
<Button> <ToggleButton value="users">
<FormattedMessage id="users" defaultMessage="Users" /> <FormattedMessage id="users" defaultMessage="Users" />
</Button> </ToggleButton>
</ButtonGroup> </ToggleButtonGroup>
); );
}; };

View File

@ -1,82 +0,0 @@
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 LogoutButton from "./LogoutButton";
import LanguageSelect from "./LanguageSelect";
interface BasicTabsProps {
removeToken: () => void;
language: string;
setLanguage: (language: string) => void;
}
const BasicTabs = (props: BasicTabsProps) => {
const routeMatch = useRouteMatch([
routes.installationWithId,
routes.alarmsWithId,
routes.usersWithId,
routes.logWithId,
]);
const id = routeMatch?.params?.id;
const intl = useIntl();
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={routeMatch?.pattern?.path ?? routes.installationWithId}
aria-label="basic tabs example"
>
<Tab
label={intl.formatMessage({
id: "installation",
defaultMessage: "Installation",
})}
value={routes.installationWithId}
component={Link}
to={routes.installation + id}
/>
<Tab
label={intl.formatMessage({
id: "alarms",
defaultMessage: "Alarms",
})}
value={routes.alarmsWithId}
component={Link}
to={routes.alarms + id}
/>
<Tab
label={intl.formatMessage({
id: "users",
defaultMessage: "Users",
})}
value={routes.usersWithId}
component={Link}
to={routes.users + id}
/>
<Tab
label={intl.formatMessage({
id: "log",
defaultMessage: "Log",
})}
value={routes.logWithId}
component={Link}
to={routes.log + id}
/>
<LanguageSelect
language={props.language}
setLanguage={props.setLanguage}
/>
<LogoutButton removeToken={props.removeToken} />
</Tabs>
</Box>
</Box>
);
};
export default BasicTabs;

View File

@ -0,0 +1,5 @@
const UserList = () => {
return <div>Userlist</div>;
};
export default UserList;

View File

@ -2,7 +2,7 @@
"alarms": "Alarms", "alarms": "Alarms",
"allInstallations": "All installations", "allInstallations": "All installations",
"applyChanges": "Apply changes", "applyChanges": "Apply changes",
"country": "country", "country": "Country",
"customerName": "Customer name", "customerName": "Customer name",
"english": "English", "english": "English",
"german": "German", "german": "German",
@ -15,4 +15,4 @@
"users": "Users", "users": "Users",
"logout": "Logout", "logout": "Logout",
"updatedSuccessfully": "Updated successfully" "updatedSuccessfully": "Updated successfully"
} }

View File

@ -13,26 +13,23 @@ const InstallationDetail = () => {
const [error, setError] = useState<AxiosError>(); const [error, setError] = useState<AxiosError>();
useEffect(() => { useEffect(() => {
setLoading(true); if (id !== "undefined") {
axiosConfig console.log(id);
.get("/GetInstallationById?id=" + id) setLoading(true);
.then((res) => { axiosConfig
setValues(res.data); .get("/GetInstallationById?id=" + id)
setLoading(false); .then((res) => {
}) setValues(res.data);
.catch((err: AxiosError) => { setLoading(false);
setError(err); })
setLoading(false); .catch((err: AxiosError) => {
}); setError(err);
setLoading(false);
});
}
}, [id]); }, [id]);
if (error) { if (values && values.id && values.id.toString() === id) {
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}
</Alert>
);
} else if (values && values.id.toString() === id) {
return ( return (
<Box sx={{ py: 3, width: 1 / 2 }}> <Box sx={{ py: 3, width: 1 / 2 }}>
<CustomerForm values={values} id={id} /> <CustomerForm values={values} id={id} />
@ -40,6 +37,12 @@ const InstallationDetail = () => {
); );
} else if (loading) { } else if (loading) {
return <CircularProgress sx={{ m: 2 }} />; return <CircularProgress sx={{ m: 2 }} />;
} else if (error) {
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}
</Alert>
);
} }
return null; return null;
}; };