diff --git a/package-lock.json b/package-lock.json
index ec8c4d4..1e18c67 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 29f8965..793212c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json
index 0deb909..caad19d 100644
--- a/public/locales/en/translations.json
+++ b/public/locales/en/translations.json
@@ -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",
diff --git a/public/locales/ro/translations.json b/public/locales/ro/translations.json
index 6bd4cbf..a70fc4f 100644
--- a/public/locales/ro/translations.json
+++ b/public/locales/ro/translations.json
@@ -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",
diff --git a/src/components/common/PageTitle.js b/src/components/common/PageTitle.js
index 474242e..c00ebc9 100644
--- a/src/components/common/PageTitle.js
+++ b/src/components/common/PageTitle.js
@@ -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 (
-
- {text}
-
+
+
+ {text}
+
+
+ {toolBar}
);
};
PageTitle.propTypes = {
- text: PropTypes.string.isRequired
+ text: PropTypes.string.isRequired,
+ toolBar: PropTypes.node
};
export default PageTitle;
diff --git a/src/components/layout/ProfileButton.js b/src/components/layout/ProfileButton.js
index 011aa00..15a53d8 100644
--- a/src/components/layout/ProfileButton.js
+++ b/src/components/layout/ProfileButton.js
@@ -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
+
+
+
+ {t("User.Profile.Label")}
+
+
-
);
diff --git a/src/components/layout/styles.js b/src/components/layout/styles.js
index 0c06cdf..2bff25a 100644
--- a/src/components/layout/styles.js
+++ b/src/components/layout/styles.js
@@ -62,6 +62,9 @@ const styles = theme => ({
content: {
flexGrow: 1,
padding: theme.spacing(2)
+ },
+ menuItemIcon: {
+ minWidth: "26px"
}
});
diff --git a/src/features/machines/components/MachinesAccordionList.js b/src/features/machines/components/MachinesAccordionList.js
new file mode 100644
index 0000000..eb394f2
--- /dev/null
+++ b/src/features/machines/components/MachinesAccordionList.js
@@ -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 (
+
+ {machines.map(machine => (
+
+ }
+ aria-label="Expand"
+ aria-controls="additional-actions1-content"
+ id="additional-actions1-header"
+ IconButtonProps={{ edge: "start" }}
+ >
+ event.stopPropagation()}
+ onFocus={event => event.stopPropagation()}
+ control={}
+ label="I acknowledge that I should stop the click event propagation"
+ />
+
+
+
+ The click event of the nested action will propagate up and expand
+ the accordion unless you explicitly stop it.
+
+
+
+ ))}
+
+ );
+};
+
+MachinesAccordionList.propTypes = {
+ machines: PropTypes.array.isRequired
+};
+
+export default MachinesAccordionList;
diff --git a/src/features/machines/components/MachinesContainer.js b/src/features/machines/components/MachinesContainer.js
index efcf509..12bbb84 100644
--- a/src/features/machines/components/MachinesContainer.js
+++ b/src/features/machines/components/MachinesContainer.js
@@ -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 (
<>
-
-
+ }
+ />
+ {viewMode === ViewModes.TABLE && (
+
+ )}
+ {viewMode === ViewModes.ACCORDION && (
+
+ )}
>
);
};
diff --git a/src/features/machines/components/MachinesList.js b/src/features/machines/components/MachinesTableList.js
similarity index 90%
rename from src/features/machines/components/MachinesList.js
rename to src/features/machines/components/MachinesTableList.js
index 82aab66..2fb8311 100644
--- a/src/features/machines/components/MachinesList.js
+++ b/src/features/machines/components/MachinesTableList.js
@@ -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 (
@@ -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;
diff --git a/src/features/machines/components/ViewModeSelection.js b/src/features/machines/components/ViewModeSelection.js
new file mode 100644
index 0000000..89e7d6c
--- /dev/null
+++ b/src/features/machines/components/ViewModeSelection.js
@@ -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 (
+
+
+
+
+
+
+
+
+ );
+};
+
+ViewModeSelection.propTypes = {
+ initialMode: PropTypes.oneOf([ViewModes.TABLE, ViewModes.ACCORDION]),
+ callback: PropTypes.func
+};
+
+export default ViewModeSelection;
diff --git a/src/features/network/components/NetworkContainer.js b/src/features/network/components/NetworkContainer.js
index 9866e45..38d03ee 100644
--- a/src/features/network/components/NetworkContainer.js
+++ b/src/features/network/components/NetworkContainer.js
@@ -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 (
-
+
-
+
);
};
diff --git a/src/features/network/state/NetworkStateProvider.js b/src/features/network/state/NetworkStateProvider.js
new file mode 100644
index 0000000..cd3a58f
--- /dev/null
+++ b/src/features/network/state/NetworkStateProvider.js
@@ -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 (
+
+
+ {children}
+
+
+ );
+};
+
+NetworkStateProvider.propTypes = {
+ children: PropTypes.node.isRequired
+};
+
+export default NetworkStateProvider;
diff --git a/src/features/network/state/contexts.js b/src/features/network/state/contexts.js
new file mode 100644
index 0000000..39a4687
--- /dev/null
+++ b/src/features/network/state/contexts.js
@@ -0,0 +1,4 @@
+import React from "react";
+
+export const NetworkStateContext = React.createContext();
+export const NetworkDispatchContext = React.createContext();
diff --git a/src/state/initialState.js b/src/features/network/state/initialState.js
similarity index 100%
rename from src/state/initialState.js
rename to src/features/network/state/initialState.js
diff --git a/src/state/reducer.js b/src/features/network/state/reducer.js
similarity index 100%
rename from src/state/reducer.js
rename to src/features/network/state/reducer.js
diff --git a/src/features/network/styles.js b/src/features/network/styles.js
deleted file mode 100644
index ec844e3..0000000
--- a/src/features/network/styles.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const styles = () => ({
- root: {
- margin: "15px"
- }
-});
-
-export default styles;
diff --git a/src/index.js b/src/index.js
index 2706ab8..1797fb6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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(
-
- Loading...}>
-
-
-
-
-
+ Loading...}>
+
+
+
+
,
diff --git a/src/providers/ApplicationStateProvider.js b/src/providers/ApplicationStateProvider.js
deleted file mode 100644
index 071541f..0000000
--- a/src/providers/ApplicationStateProvider.js
+++ /dev/null
@@ -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 (
-
-
- {children}
-
-
- );
-};
-
-ApplicationStateProvider.propTypes = {
- children: PropTypes.node.isRequired
-};
-
-export default ApplicationStateProvider;
diff --git a/src/state/contexts.js b/src/state/contexts.js
deleted file mode 100644
index 229f959..0000000
--- a/src/state/contexts.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import React from "react";
-
-export const ApplicationStateContext = React.createContext();
-export const ApplicationDispatchContext = React.createContext();