machines view mode option

master
Tudor Stanciu 2023-03-21 18:14:32 +02:00
parent 234778cc43
commit 3cc5d8f6f3
20 changed files with 257 additions and 86 deletions

17
package-lock.json generated
View File

@ -1315,6 +1315,11 @@
"resolved": "https://lab.code-rove.com/public-node-registry/@flare/js-utils/-/js-utils-1.0.3.tgz",
"integrity": "sha512-VgXQHoQEVZ/71B6YQHQP8/Yd/w1smGD+kCCiNvJKZ1xMD3nkN9mjoHxIqbOJMZ2q5PZlV6gXYT7eVol8Wm+D0A=="
},
"@flare/react-hooks": {
"version": "1.0.1",
"resolved": "https://lab.code-rove.com/public-node-registry/@flare/react-hooks/-/react-hooks-1.0.1.tgz",
"integrity": "sha512-mU6zkdETonWv0SnxT1qF/lch6ND1yph6bAw+BaYxl/jj8tasOune+KKfyGcLFR/Kso3q8PlVPT8x4CiLiO6woQ=="
},
"@flare/tuitio-client": {
"version": "1.1.0",
"resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client/-/tuitio-client-1.1.0.tgz",
@ -1980,6 +1985,18 @@
"@babel/runtime": "^7.4.4"
}
},
"@material-ui/lab": {
"version": "4.0.0-alpha.61",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.61.tgz",
"integrity": "sha512-rSzm+XKiNUjKegj8bzt5+pygZeckNLOr+IjykH8sYdVk7dE9y2ZuUSofiMV2bJk3qU+JHwexmw+q0RyNZB9ugg==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.3",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@material-ui/styles": {
"version": "4.11.5",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz",

View File

@ -13,10 +13,12 @@
},
"private": true,
"dependencies": {
"@flare/react-hooks": "^1.0.1",
"@flare/js-utils": "^1.0.3",
"@flare/tuitio-client-react": "^1.1.1",
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.61",
"axios": "^1.3.4",
"i18next": "^19.4.4",
"i18next-browser-languagedetector": "^4.1.1",

View File

@ -30,15 +30,16 @@
"Username": "Username",
"Password": "Password",
"Label": "Login",
"Logout": "Logout",
"IncorrectCredentials": "Incorrect credentials."
},
"User": {
"Profile": {
"Label": "Profile",
"Hello": "Hi, {{userName}}",
"Description": "{{userName}}, authenticated on {{loginDate}}",
"OpenPortfolio": "Open portfolio"
}
},
"Logout": "Logout"
},
"Machine": {
"FullName": "Full machine name",

View File

@ -21,15 +21,16 @@
"Username": "Utilizator",
"Password": "Parolă",
"Label": "Autentificare",
"Logout": "Deconectare",
"IncorrectCredentials": "Credențiale incorecte."
},
"User": {
"Profile": {
"Label": "Profil",
"Hello": "Salut, {{userName}}",
"Description": "{{userName}}, autentificat pe {{loginDate}}",
"OpenPortfolio": "Deschide portofoliu"
}
},
"Logout": "Deconectare"
},
"Machine": {
"FullName": "Nume intreg masina",

View File

@ -7,26 +7,36 @@ const useStyles = makeStyles(theme => ({
box: {
display: "flex",
justifyContent: "space-between",
marginBottom: theme.spacing(2),
marginBottom: theme.spacing(1),
marginTop: theme.spacing(0)
},
title: { textTransform: "uppercase" }
title: {
display: "flex",
justifyContent: "center",
flexDirection: "column",
minHeight: "40px"
},
titleText: { textTransform: "uppercase" }
}));
const PageTitle = ({ text }) => {
const PageTitle = ({ text, toolBar }) => {
const classes = useStyles();
return (
<div className={classes.box}>
<Typography className={classes.title} variant="h3" size="sm">
<div className={classes.title}>
<Typography className={classes.titleText} variant="h3" size="sm">
{text}
</Typography>
</div>
{toolBar}
</div>
);
};
PageTitle.propTypes = {
text: PropTypes.string.isRequired
text: PropTypes.string.isRequired,
toolBar: PropTypes.node
};
export default PageTitle;

View File

@ -1,13 +1,28 @@
import React, { useState } from "react";
import { IconButton, Menu, MenuItem } from "@material-ui/core";
import {
IconButton,
Menu,
MenuItem,
Typography,
ListItemIcon
} from "@material-ui/core";
import AccountCircle from "@material-ui/icons/AccountCircle";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import AccountBoxIcon from "@material-ui/icons/AccountBox";
import { useHistory } from "react-router-dom";
import { useTuitioClient } from "@flare/tuitio-client-react";
import { useToast } from "../../hooks";
import styles from "./styles";
import { makeStyles } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles(styles);
const ProfileButton = () => {
const history = useHistory();
const { error } = useToast();
const classes = useStyles();
const { t } = useTranslation();
const { logout } = useTuitioClient({
onLogoutFailed: errorMessage => error(errorMessage),
@ -57,9 +72,17 @@ const ProfileButton = () => {
handleClose();
}}
>
Profile
<ListItemIcon className={classes.menuItemIcon}>
<AccountBoxIcon fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">{t("User.Profile.Label")}</Typography>
</MenuItem>
<MenuItem onClick={logout}>
<ListItemIcon className={classes.menuItemIcon}>
<ExitToAppIcon fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">{t("User.Logout")}</Typography>
</MenuItem>
<MenuItem onClick={logout}>Logout</MenuItem>
</Menu>
</div>
);

View File

@ -62,6 +62,9 @@ const styles = theme => ({
content: {
flexGrow: 1,
padding: theme.spacing(2)
},
menuItemIcon: {
minWidth: "26px"
}
});

View File

@ -0,0 +1,63 @@
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Typography from "@material-ui/core/Typography";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import withStyles from "@material-ui/core/styles/withStyles";
const IconLeftAccordionSummary = withStyles({
expandIcon: {
order: -1
}
})(AccordionSummary);
const useStyles = makeStyles({
root: {
width: "100%"
}
});
const MachinesAccordionList = ({ machines }) => {
const classes = useStyles();
return (
<div className={classes.root}>
{machines.map(machine => (
<Accordion key={`machine-${machine.machineId}`}>
<IconLeftAccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-label="Expand"
aria-controls="additional-actions1-content"
id="additional-actions1-header"
IconButtonProps={{ edge: "start" }}
>
<FormControlLabel
aria-label="Acknowledge"
onClick={event => event.stopPropagation()}
onFocus={event => event.stopPropagation()}
control={<Checkbox />}
label="I acknowledge that I should stop the click event propagation"
/>
</IconLeftAccordionSummary>
<AccordionDetails>
<Typography color="textSecondary">
The click event of the nested action will propagate up and expand
the accordion unless you explicitly stop it.
</Typography>
</AccordionDetails>
</Accordion>
))}
</div>
);
};
MachinesAccordionList.propTypes = {
machines: PropTypes.array.isRequired
};
export default MachinesAccordionList;

View File

@ -1,16 +1,20 @@
import React, { useContext, useEffect, useCallback } from "react";
import React, { useContext, useEffect, useCallback, useState } from "react";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../../../state/contexts";
NetworkStateContext,
NetworkDispatchContext
} from "../../network/state/contexts";
import useApi from "../../../api";
import MachinesList from "./MachinesList";
import MachinesTableList from "./MachinesTableList";
import MachinesAccordionList from "./MachinesAccordionList";
import PageTitle from "../../../components/common/PageTitle";
import { useTranslation } from "react-i18next";
import ViewModeSelection, { ViewModes } from "./ViewModeSelection";
const MachinesContainer = () => {
const state = useContext(ApplicationStateContext);
const dispatchActions = useContext(ApplicationDispatchContext);
const [viewMode, setViewMode] = useState(ViewModes.TABLE);
const state = useContext(NetworkStateContext);
const dispatchActions = useContext(NetworkDispatchContext);
const { t } = useTranslation();
const api = useApi();
@ -32,8 +36,16 @@ const MachinesContainer = () => {
return (
<>
<PageTitle text={t("Menu.Machines")} />
<MachinesList dense={true} machines={state.network.machines} />
<PageTitle
text={t("Menu.Machines")}
toolBar={<ViewModeSelection callback={setViewMode} />}
/>
{viewMode === ViewModes.TABLE && (
<MachinesTableList dense={true} machines={state.network.machines} />
)}
{viewMode === ViewModes.ACCORDION && (
<MachinesAccordionList machines={state.network.machines} />
)}
</>
);
};

View File

@ -12,7 +12,7 @@ import Paper from "@material-ui/core/Paper";
import MachineContainer from "./MachineContainer";
import { useTranslation } from "react-i18next";
const MachinesList = ({ dense, machines }) => {
const MachinesTableList = ({ dense, machines }) => {
const { t } = useTranslation();
return (
<TableContainer component={Paper}>
@ -40,9 +40,9 @@ const MachinesList = ({ dense, machines }) => {
);
};
MachinesList.propTypes = {
MachinesTableList.propTypes = {
dense: PropTypes.bool.isRequired,
machines: PropTypes.array.isRequired
};
export default MachinesList;
export default MachinesTableList;

View File

@ -0,0 +1,60 @@
import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import TableChartIcon from "@material-ui/icons/TableChart";
import ViewListIcon from "@material-ui/icons/ViewList";
import ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import { useWindowSize } from "@flare/react-hooks";
export const ViewModes = {
TABLE: "table",
ACCORDION: "accordion"
};
const ViewModeSelection = ({ callback }) => {
const [viewMode, setViewMode] = useState(ViewModes.TABLE);
const { isMobile } = useWindowSize();
const handleViewModeSelection = useCallback(
(event, mode) => {
setViewMode(mode);
callback && callback(mode);
},
[callback]
);
useEffect(
() =>
handleViewModeSelection(
null,
isMobile ? ViewModes.ACCORDION : ViewModes.TABLE
),
[handleViewModeSelection, isMobile]
);
return (
<ToggleButtonGroup
size="small"
value={viewMode}
exclusive
onChange={handleViewModeSelection}
>
<ToggleButton value={ViewModes.TABLE} aria-label="table view mode">
<TableChartIcon />
</ToggleButton>
<ToggleButton
value={ViewModes.ACCORDION}
aria-label="accordion view mode"
>
<ViewListIcon />
</ToggleButton>
</ToggleButtonGroup>
);
};
ViewModeSelection.propTypes = {
initialMode: PropTypes.oneOf([ViewModes.TABLE, ViewModes.ACCORDION]),
callback: PropTypes.func
};
export default ViewModeSelection;

View File

@ -1,17 +1,12 @@
import React from "react";
import MachinesContainer from "../../machines/components/MachinesContainer";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../styles";
const useStyles = makeStyles(styles);
import NetworkStateProvider from "../state/NetworkStateProvider";
const NetworkContainer = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<NetworkStateProvider>
<MachinesContainer />
</div>
</NetworkStateProvider>
);
};

View File

@ -0,0 +1,27 @@
import React, { useReducer, useMemo } from "react";
import PropTypes from "prop-types";
import { NetworkStateContext, NetworkDispatchContext } from "./contexts";
import { reducer, dispatchActions as reducerDispatchActions } from "./reducer";
import { initialState } from "./initialState";
const NetworkStateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const dispatchActions = useMemo(
() => reducerDispatchActions(dispatch),
[dispatch]
);
return (
<NetworkStateContext.Provider value={state}>
<NetworkDispatchContext.Provider value={dispatchActions}>
{children}
</NetworkDispatchContext.Provider>
</NetworkStateContext.Provider>
);
};
NetworkStateProvider.propTypes = {
children: PropTypes.node.isRequired
};
export default NetworkStateProvider;

View File

@ -0,0 +1,4 @@
import React from "react";
export const NetworkStateContext = React.createContext();
export const NetworkDispatchContext = React.createContext();

View File

@ -1,7 +0,0 @@
const styles = () => ({
root: {
margin: "15px"
}
});
export default styles;

View File

@ -4,7 +4,6 @@ import ThemeProvider from "./providers/ThemeProvider";
import CssBaseline from "@material-ui/core/CssBaseline";
import App from "./components/App";
import { TuitioProvider } from "@flare/tuitio-client-react";
import ApplicationStateProvider from "./providers/ApplicationStateProvider";
import ToastProvider from "./providers/ToastProvider";
import SensitiveInfoProvider from "./providers/SensitiveInfoProvider";
import "./utils/i18n";
@ -14,13 +13,11 @@ ReactDOM.render(
<ThemeProvider>
<CssBaseline />
<SensitiveInfoProvider>
<ApplicationStateProvider>
<Suspense fallback={<div>Loading...</div>}>
<ToastProvider>
<App />
</ToastProvider>
</Suspense>
</ApplicationStateProvider>
</SensitiveInfoProvider>
</ThemeProvider>
</TuitioProvider>,

View File

@ -1,33 +0,0 @@
import React, { useReducer, useMemo } from "react";
import PropTypes from "prop-types";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../state/contexts";
import {
reducer,
dispatchActions as reducerDispatchActions
} from "../state/reducer";
import { initialState } from "../state/initialState";
const ApplicationStateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const dispatchActions = useMemo(
() => reducerDispatchActions(dispatch),
[dispatch]
);
return (
<ApplicationStateContext.Provider value={state}>
<ApplicationDispatchContext.Provider value={dispatchActions}>
{children}
</ApplicationDispatchContext.Provider>
</ApplicationStateContext.Provider>
);
};
ApplicationStateProvider.propTypes = {
children: PropTypes.node.isRequired
};
export default ApplicationStateProvider;

View File

@ -1,4 +0,0 @@
import React from "react";
export const ApplicationStateContext = React.createContext();
export const ApplicationDispatchContext = React.createContext();