diff options
73 files changed, 1185 insertions, 1137 deletions
diff --git a/.github/workflows/new.yml b/.github/workflows/new.yml index 58956106..cf79e131 100644 --- a/.github/workflows/new.yml +++ b/.github/workflows/new.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - - run: npm install + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - run: npm install --force working-directory: modern - run: npm run lint working-directory: modern diff --git a/modern/.eslintrc.js b/modern/.eslintrc.js index f380f4c6..6cef63d8 100644 --- a/modern/.eslintrc.js +++ b/modern/.eslintrc.js @@ -15,7 +15,9 @@ module.exports = { 'no-prototype-builtins': [0], 'no-nested-ternary': [0], 'operator-linebreak': [0], + 'import/no-unresolved': [0], 'react/jsx-filename-extension': [1, { extensions: ['.js'] }], + 'react/function-component-definition': [1, { namedComponents: 'arrow-function', unnamedComponents: 'arrow-function' }], 'react/prop-types': [0], 'react/jsx-props-no-spreading': [0], 'jsx-a11y/anchor-is-valid': [0], diff --git a/modern/package.json b/modern/package.json index 771a786b..320d3411 100644 --- a/modern/package.json +++ b/modern/package.json @@ -4,28 +4,30 @@ "private": true, "dependencies": { "@craco/craco": "^5.9.0", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", "@mapbox/mapbox-gl-draw": "^1.3.0", - "@material-ui/core": "^4.12.4", - "@material-ui/icons": "^4.11.3", - "@material-ui/lab": "^4.0.0-alpha.58", - "@reduxjs/toolkit": "^1.6.0", + "@mui/icons-material": "^5.8.0", + "@mui/lab": "^5.0.0-alpha.82", + "@mui/material": "^5.8.0", + "@mui/styles": "^5.8.0", + "@reduxjs/toolkit": "^1.8.1", "@tmcw/togeojson": "^4.5.0", "@turf/circle": "^6.5.0", - "@turf/turf": "^6.4.0", - "mapbox-gl": "^1.13.1", - "maplibre-gl": "^1.15.0", + "@turf/turf": "^6.5.0", + "mapbox-gl": "^1.13.2", + "maplibre-gl": "^2.1.9", "material-ui-dropzone": "^3.5.0", - "moment": "^2.29.1", + "moment": "^2.29.3", "react": "^17.0.2", - "react-container-dimensions": "^1.4.1", "react-dom": "^17.0.2", - "react-redux": "^7.2.4", - "react-router-dom": "^5.2.0", + "react-redux": "^8.0.2", + "react-router-dom": "^6.3.0", "react-scripts": "^3.4.4", "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", - "recharts": "^2.0.9", - "redux": "^4.1.0", + "recharts": "^2.1.10", + "redux": "^4.2.0", "typeface-roboto": "1.1.13", "wellknown": "^0.5.0" }, @@ -50,10 +52,10 @@ ] }, "devDependencies": { - "eslint": "^7.30.0", - "eslint-config-airbnb": "^18.2.1", - "eslint-plugin-import": "^2.23.4", - "eslint-plugin-react": "^7.24.0", - "eslint-plugin-react-hooks": "^1.7.0" + "eslint": "^8.16.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-react": "^7.30.0", + "eslint-plugin-react-hooks": "^4.5.0" } } diff --git a/modern/src/App.js b/modern/src/App.js index fee94e5b..3ba6b2e1 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -1,56 +1,10 @@ -import React, { useState } from 'react'; -import { ThemeProvider } from '@material-ui/core/styles'; -import { Switch, Route, useHistory } from 'react-router-dom'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import { useDispatch, useSelector } from 'react-redux'; -import { makeStyles, LinearProgress, useMediaQuery } from '@material-ui/core'; -import MainPage from './main/MainPage'; -import RouteReportPage from './reports/RouteReportPage'; -import ServerPage from './settings/ServerPage'; -import UsersPage from './settings/UsersPage'; -import DevicePage from './settings/DevicePage'; -import UserPage from './settings/UserPage'; -import SocketController from './SocketController'; -import NotificationsPage from './settings/NotificationsPage'; -import NotificationPage from './settings/NotificationPage'; -import GroupsPage from './settings/GroupsPage'; -import GroupPage from './settings/GroupPage'; -import PositionPage from './other/PositionPage'; -import EventReportPage from './reports/EventReportPage'; -import ReplayPage from './other/ReplayPage'; -import TripReportPage from './reports/TripReportPage'; -import StopReportPage from './reports/StopReportPage'; -import SummaryReportPage from './reports/SummaryReportPage'; -import ChartReportPage from './reports/ChartReportPage'; -import DriversPage from './settings/DriversPage'; -import DriverPage from './settings/DriverPage'; -import CalendarsPage from './settings/CalendarsPage'; -import CalendarPage from './settings/CalendarPage'; -import ComputedAttributesPage from './settings/ComputedAttributesPage'; -import ComputedAttributePage from './settings/ComputedAttributePage'; -import MaintenancesPage from './settings/MaintenancesPage'; -import MaintenancePage from './settings/MaintenancePage'; -import CommandsPage from './settings/CommandsPage'; -import CommandPage from './settings/CommandPage'; -import StatisticsPage from './reports/StatisticsPage'; -import CachingController from './CachingController'; - -import LoginPage from './login/LoginPage'; -import RegisterPage from './login/RegisterPage'; -import ResetPasswordPage from './login/ResetPasswordPage'; - +import React from 'react'; +import { Outlet } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { LinearProgress, useMediaQuery } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import theme from './common/theme'; -import GeofencesPage from './other/GeofencesPage'; -import GeofencePage from './settings/GeofencePage'; -import useQuery from './common/util/useQuery'; -import { useEffectAsync } from './reactHelper'; -import { devicesActions } from './store'; -import EventPage from './other/EventPage'; -import PreferencesPage from './settings/PreferencesPage'; import BottomMenu from './common/components/BottomMenu'; -import AccumulatorsPage from './settings/AccumulatorsPage'; -import CommandSendPage from './settings/CommandSendPage'; -import ErrorHandler from './common/components/ErrorHandler'; const useStyles = makeStyles(() => ({ page: { @@ -63,107 +17,27 @@ const useStyles = makeStyles(() => ({ })); const App = () => { - const history = useHistory(); - const dispatch = useDispatch(); const classes = useStyles(); const desktop = useMediaQuery(theme.breakpoints.up('md')); const initialized = useSelector((state) => !!state.session.server && !!state.session.user); - const [redirectsHandled, setRedirectsHandled] = useState(false); - - const query = useQuery(); - - useEffectAsync(async () => { - if (query.get('token')) { - const token = query.get('token'); - await fetch(`/api/session?token=${encodeURIComponent(token)}`); - history.push('/'); - } else if (query.get('deviceId')) { - const deviceId = query.get('deviceId'); - const response = await fetch(`/api/devices?uniqueId=${deviceId}`); - if (response.ok) { - const items = await response.json(); - if (items.length > 0) { - dispatch(devicesActions.select(items[0].id)); - } - } else { - throw Error(await response.text()); - } - history.push('/'); - } else if (query.get('eventId')) { - const eventId = parseInt(query.get('eventId'), 10); - history.push(`/event/${eventId}`); - } else { - setRedirectsHandled(true); - } - }, [query]); - - return (!redirectsHandled ? (<LinearProgress />) : ( - <ThemeProvider theme={theme}> - <CssBaseline /> - <SocketController /> - <CachingController /> - <Switch> - <Route exact path="/login" component={LoginPage} /> - <Route exact path="/register" component={RegisterPage} /> - <Route exact path="/reset-password" component={ResetPasswordPage} /> - <Route> - {!initialized ? (<LinearProgress />) : ( - <> - <div className={classes.page}> - <Switch> - <Route exact path="/" component={MainPage} /> - - <Route exact path="/position/:id?" component={PositionPage} /> - <Route exact path="/event/:id?" component={EventPage} /> - <Route exact path="/replay" component={ReplayPage} /> - <Route exact path="/geofences" component={GeofencesPage} /> - - <Route exact path="/settings/accumulators/:deviceId?" component={AccumulatorsPage} /> - <Route exact path="/settings/calendars" component={CalendarsPage} /> - <Route exact path="/settings/calendar/:id?" component={CalendarPage} /> - <Route exact path="/settings/commands" component={CommandsPage} /> - <Route exact path="/settings/command/:id?" component={CommandPage} /> - <Route exact path="/settings/command-send/:deviceId?" component={CommandSendPage} /> - <Route exact path="/settings/attributes" component={ComputedAttributesPage} /> - <Route exact path="/settings/attribute/:id?" component={ComputedAttributePage} /> - <Route exact path="/settings/device/:id?" component={DevicePage} /> - <Route exact path="/settings/drivers" component={DriversPage} /> - <Route exact path="/settings/driver/:id?" component={DriverPage} /> - <Route exact path="/settings/geofence/:id?" component={GeofencePage} /> - <Route exact path="/settings/groups" component={GroupsPage} /> - <Route exact path="/settings/group/:id?" component={GroupPage} /> - <Route exact path="/settings/maintenances" component={MaintenancesPage} /> - <Route exact path="/settings/maintenance/:id?" component={MaintenancePage} /> - <Route exact path="/settings/notifications" component={NotificationsPage} /> - <Route exact path="/settings/notification/:id?" component={NotificationPage} /> - <Route exact path="/settings/preferences" component={PreferencesPage} /> - <Route exact path="/settings/server" component={ServerPage} /> - <Route exact path="/settings/users" component={UsersPage} /> - <Route exact path="/settings/user/:id?" component={UserPage} /> - <Route exact path="/reports/chart" component={ChartReportPage} /> - <Route exact path="/reports/event" component={EventReportPage} /> - <Route exact path="/reports/route" component={RouteReportPage} /> - <Route exact path="/reports/statistics" component={StatisticsPage} /> - <Route exact path="/reports/stop" component={StopReportPage} /> - <Route exact path="/reports/summary" component={SummaryReportPage} /> - <Route exact path="/reports/trip" component={TripReportPage} /> - </Switch> - </div> - {!desktop && ( - <div className={classes.menu}> - <BottomMenu /> - </div> - )} - </> - )} - </Route> - </Switch> - <ErrorHandler /> - </ThemeProvider> - )); + if (!initialized) { + return (<LinearProgress />); + } + return ( + <> + <div className={classes.page}> + <Outlet /> + </div> + {!desktop && ( + <div className={classes.menu}> + <BottomMenu /> + </div> + )} + </> + ); }; export default App; diff --git a/modern/src/Navigation.js b/modern/src/Navigation.js new file mode 100644 index 00000000..b4156713 --- /dev/null +++ b/modern/src/Navigation.js @@ -0,0 +1,145 @@ +import React, { useState } from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { LinearProgress } from '@mui/material'; +import MainPage from './main/MainPage'; +import RouteReportPage from './reports/RouteReportPage'; +import ServerPage from './settings/ServerPage'; +import UsersPage from './settings/UsersPage'; +import DevicePage from './settings/DevicePage'; +import UserPage from './settings/UserPage'; +import NotificationsPage from './settings/NotificationsPage'; +import NotificationPage from './settings/NotificationPage'; +import GroupsPage from './settings/GroupsPage'; +import GroupPage from './settings/GroupPage'; +import PositionPage from './other/PositionPage'; +import EventReportPage from './reports/EventReportPage'; +import ReplayPage from './other/ReplayPage'; +import TripReportPage from './reports/TripReportPage'; +import StopReportPage from './reports/StopReportPage'; +import SummaryReportPage from './reports/SummaryReportPage'; +import ChartReportPage from './reports/ChartReportPage'; +import DriversPage from './settings/DriversPage'; +import DriverPage from './settings/DriverPage'; +import CalendarsPage from './settings/CalendarsPage'; +import CalendarPage from './settings/CalendarPage'; +import ComputedAttributesPage from './settings/ComputedAttributesPage'; +import ComputedAttributePage from './settings/ComputedAttributePage'; +import MaintenancesPage from './settings/MaintenancesPage'; +import MaintenancePage from './settings/MaintenancePage'; +import CommandsPage from './settings/CommandsPage'; +import CommandPage from './settings/CommandPage'; +import StatisticsPage from './reports/StatisticsPage'; +import LoginPage from './login/LoginPage'; +import RegisterPage from './login/RegisterPage'; +import ResetPasswordPage from './login/ResetPasswordPage'; +import GeofencesPage from './other/GeofencesPage'; +import GeofencePage from './settings/GeofencePage'; +import useQuery from './common/util/useQuery'; +import { useEffectAsync } from './reactHelper'; +import { devicesActions } from './store'; +import EventPage from './other/EventPage'; +import PreferencesPage from './settings/PreferencesPage'; +import AccumulatorsPage from './settings/AccumulatorsPage'; +import CommandSendPage from './settings/CommandSendPage'; +import App from './App'; + +const Navigation = () => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + const [redirectsHandled, setRedirectsHandled] = useState(false); + + const query = useQuery(); + + useEffectAsync(async () => { + if (query.get('token')) { + const token = query.get('token'); + await fetch(`/api/session?token=${encodeURIComponent(token)}`); + navigate('/'); + } else if (query.get('deviceId')) { + const deviceId = query.get('deviceId'); + const response = await fetch(`/api/devices?uniqueId=${deviceId}`); + if (response.ok) { + const items = await response.json(); + if (items.length > 0) { + dispatch(devicesActions.select(items[0].id)); + } + } else { + throw Error(await response.text()); + } + navigate('/'); + } else if (query.get('eventId')) { + const eventId = parseInt(query.get('eventId'), 10); + navigate(`/event/${eventId}`); + } else { + setRedirectsHandled(true); + } + }, [query]); + + if (!redirectsHandled) { + return (<LinearProgress />); + } + return ( + <Routes> + <Route path="/login" element={<LoginPage />} /> + <Route path="/register" element={<RegisterPage />} /> + <Route path="/reset-password" element={<ResetPasswordPage />} /> + <Route path="/" element={<App />}> + <Route index element={<MainPage />} /> + + <Route path="position/:id" element={<PositionPage />} /> + <Route path="event/:id" element={<EventPage />} /> + <Route path="replay" element={<ReplayPage />} /> + <Route path="geofences" element={<GeofencesPage />} /> + + <Route path="settings"> + <Route path="accumulators/:deviceId" element={<AccumulatorsPage />} /> + <Route path="calendars" element={<CalendarsPage />} /> + <Route path="calendar/:id" element={<CalendarPage />} /> + <Route path="calendar" element={<CalendarPage />} /> + <Route path="commands" element={<CommandsPage />} /> + <Route path="command/:id" element={<CommandPage />} /> + <Route path="command" element={<CommandPage />} /> + <Route path="command-send/:deviceId" element={<CommandSendPage />} /> + <Route path="attributes" element={<ComputedAttributesPage />} /> + <Route path="attribute/:id" element={<ComputedAttributePage />} /> + <Route path="attribute" element={<ComputedAttributePage />} /> + <Route path="device/:id" element={<DevicePage />} /> + <Route path="device" element={<DevicePage />} /> + <Route path="drivers" element={<DriversPage />} /> + <Route path="driver/:id" element={<DriverPage />} /> + <Route path="driver" element={<DriverPage />} /> + <Route path="geofence/:id" element={<GeofencePage />} /> + <Route path="geofence" element={<GeofencePage />} /> + <Route path="groups" element={<GroupsPage />} /> + <Route path="group/:id" element={<GroupPage />} /> + <Route path="group" element={<GroupPage />} /> + <Route path="maintenances" element={<MaintenancesPage />} /> + <Route path="maintenance/:id" element={<MaintenancePage />} /> + <Route path="maintenance" element={<MaintenancePage />} /> + <Route path="notifications" element={<NotificationsPage />} /> + <Route path="notification/:id" element={<NotificationPage />} /> + <Route path="notification" element={<NotificationPage />} /> + <Route path="preferences" element={<PreferencesPage />} /> + <Route path="server" element={<ServerPage />} /> + <Route path="users" element={<UsersPage />} /> + <Route path="user/:id" element={<UserPage />} /> + <Route path="user" element={<UserPage />} /> + </Route> + + <Route path="reports"> + <Route path="chart" element={<ChartReportPage />} /> + <Route path="event" element={<EventReportPage />} /> + <Route path="route" element={<RouteReportPage />} /> + <Route path="statistics" element={<StatisticsPage />} /> + <Route path="stop" element={<StopReportPage />} /> + <Route path="summary" element={<SummaryReportPage />} /> + <Route path="trip" element={<TripReportPage />} /> + </Route> + </Route> + </Routes> + ); +}; + +export default Navigation; diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js index 9826d4b1..57ffa0d5 100644 --- a/modern/src/SocketController.js +++ b/modern/src/SocketController.js @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector, connect } from 'react-redux'; -import { Snackbar } from '@material-ui/core'; -import { useHistory } from 'react-router-dom'; +import { Snackbar } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; import { positionsActions, devicesActions, sessionActions } from './store'; import { useEffectAsync } from './reactHelper'; @@ -11,7 +11,7 @@ import { snackBarDurationLongMs } from './common/util/duration'; const SocketController = () => { const dispatch = useDispatch(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); const authenticated = useSelector((state) => !!state.session.user); @@ -74,7 +74,7 @@ const SocketController = () => { if (response.ok) { dispatch(sessionActions.updateUser(await response.json())); } else { - history.push('/login'); + navigate('/login'); } return null; }, [authenticated]); diff --git a/modern/src/common/components/BottomMenu.js b/modern/src/common/components/BottomMenu.js index 30960396..b011638e 100644 --- a/modern/src/common/components/BottomMenu.js +++ b/modern/src/common/components/BottomMenu.js @@ -1,22 +1,22 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { Paper, BottomNavigation, BottomNavigationAction, Menu, MenuItem, Typography, -} from '@material-ui/core'; +} from '@mui/material'; -import DescriptionIcon from '@material-ui/icons/Description'; -import SettingsIcon from '@material-ui/icons/Settings'; -import MapIcon from '@material-ui/icons/Map'; -import PersonIcon from '@material-ui/icons/Person'; -import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import DescriptionIcon from '@mui/icons-material/Description'; +import SettingsIcon from '@mui/icons-material/Settings'; +import MapIcon from '@mui/icons-material/Map'; +import PersonIcon from '@mui/icons-material/Person'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import { sessionActions } from '../../store'; import { useTranslation } from './LocalizationProvider'; import { useReadonly } from '../util/permissions'; const BottomMenu = () => { - const history = useHistory(); + const navigate = useNavigate(); const location = useLocation(); const dispatch = useDispatch(); const t = useTranslation(); @@ -41,26 +41,26 @@ const BottomMenu = () => { const handleAccount = () => { setAnchorEl(null); - history.push(`/settings/user/${userId}`); + navigate(`/settings/user/${userId}`); }; const handleLogout = async () => { setAnchorEl(null); await fetch('/api/session', { method: 'DELETE' }); - history.push('/login'); + navigate('/login'); dispatch(sessionActions.updateUser(null)); }; const handleSelection = (event, value) => { switch (value) { case 0: - history.push('/'); + navigate('/'); break; case 1: - history.push('/reports/route'); + navigate('/reports/route'); break; case 2: - history.push('/settings/preferences'); + navigate('/settings/preferences'); break; case 3: if (readonly) { diff --git a/modern/src/common/components/ErrorHandler.js b/modern/src/common/components/ErrorHandler.js index 5a238956..757a78ef 100644 --- a/modern/src/common/components/ErrorHandler.js +++ b/modern/src/common/components/ErrorHandler.js @@ -1,5 +1,4 @@ -import { Snackbar } from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; +import { Snackbar, Alert } from '@mui/material'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { usePrevious } from '../../reactHelper'; @@ -15,7 +14,6 @@ const ErrorHandler = () => { <Snackbar open={!!error}> <Alert elevation={6} - variant="filled" onClose={() => dispatch(errorsActions.pop())} severity="error" > diff --git a/modern/src/common/components/LinkField.js b/modern/src/common/components/LinkField.js index 0f6cc7ba..f87a8963 100644 --- a/modern/src/common/components/LinkField.js +++ b/modern/src/common/components/LinkField.js @@ -1,12 +1,11 @@ import { FormControl, InputLabel, MenuItem, Select, -} from '@material-ui/core'; +} from '@mui/material'; import React, { useState } from 'react'; import { useEffectAsync } from '../../reactHelper'; const LinkField = ({ margin, - variant, label, endpointAll, endpointLinked, @@ -69,9 +68,10 @@ const LinkField = ({ if (items && linked) { return ( - <FormControl margin={margin} variant={variant}> + <FormControl margin={margin} fullWidth> <InputLabel>{label}</InputLabel> <Select + label={label} multiple value={linked} onChange={onChange} diff --git a/modern/src/common/components/LocalizationProvider.js b/modern/src/common/components/LocalizationProvider.js index 0df242dc..147ac5ca 100644 --- a/modern/src/common/components/LocalizationProvider.js +++ b/modern/src/common/components/LocalizationProvider.js @@ -1,5 +1,5 @@ +/* eslint-disable import/no-relative-packages */ import React, { createContext, useContext, useMemo } from 'react'; -import usePersistedState from '../util/usePersistedState'; import af from '../../../../web/l10n/af.json'; import ar from '../../../../web/l10n/ar.json'; @@ -55,6 +55,7 @@ import uz from '../../../../web/l10n/uz.json'; import vi from '../../../../web/l10n/vi.json'; import zh from '../../../../web/l10n/zh.json'; import zhTW from '../../../../web/l10n/zh_TW.json'; +import usePersistedState from '../util/usePersistedState'; const languages = { af: { data: af, name: 'Afrikaans' }, @@ -143,8 +144,10 @@ const LocalizationContext = createContext({ export const LocalizationProvider = ({ children }) => { const [language, setLanguage] = usePersistedState('language', getDefaultLanguage()); + const value = useMemo(() => ({ languages, language, setLanguage }), [languages, language, setLanguage]); + return ( - <LocalizationContext.Provider value={{ languages, language, setLanguage }}> + <LocalizationContext.Provider value={value}> {children} </LocalizationContext.Provider> ); diff --git a/modern/src/common/components/NavBar.js b/modern/src/common/components/NavBar.js index ac689e76..a1a73fdf 100644 --- a/modern/src/common/components/NavBar.js +++ b/modern/src/common/components/NavBar.js @@ -1,13 +1,17 @@ import React from 'react'; import { AppBar, Toolbar, Typography, IconButton, -} from '@material-ui/core'; -import MenuIcon from '@material-ui/icons/Menu'; +} from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; const Navbar = ({ setOpenDrawer, title }) => ( <AppBar position="sticky" color="inherit"> <Toolbar> - <IconButton color="inherit" edge="start" onClick={() => setOpenDrawer(true)}> + <IconButton + color="inherit" + edge="start" + onClick={() => setOpenDrawer(true)} + > <MenuIcon /> </IconButton> <Typography variant="h6" noWrap> diff --git a/modern/src/common/components/PageLayout.js b/modern/src/common/components/PageLayout.js index a1f117c4..17567fb1 100644 --- a/modern/src/common/components/PageLayout.js +++ b/modern/src/common/components/PageLayout.js @@ -1,10 +1,20 @@ import React, { useState } from 'react'; import { - AppBar, Breadcrumbs, Divider, Drawer, Hidden, IconButton, makeStyles, Toolbar, Typography, useMediaQuery, useTheme, -} from '@material-ui/core'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import MenuIcon from '@material-ui/icons/Menu'; -import { useHistory } from 'react-router-dom'; + AppBar, + Breadcrumbs, + Divider, + Drawer, + Hidden, + IconButton, + Toolbar, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import MenuIcon from '@mui/icons-material/Menu'; +import { useNavigate } from 'react-router-dom'; import { useTranslation } from './LocalizationProvider'; const useStyles = makeStyles((theme) => ({ @@ -56,13 +66,13 @@ const PageTitle = ({ breadcrumbs }) => { const PageLayout = ({ menu, breadcrumbs, children }) => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const [openDrawer, setOpenDrawer] = useState(false); return ( <> - <Hidden smDown> + <Hidden mdDown> <div className={classes.desktopRoot}> <Drawer variant="permanent" @@ -71,7 +81,7 @@ const PageLayout = ({ menu, breadcrumbs, children }) => { > <div className={classes.toolbar}> <Toolbar> - <IconButton color="inherit" edge="start" onClick={() => history.push('/')}> + <IconButton color="inherit" edge="start" onClick={() => navigate('/')}> <ArrowBackIcon /> </IconButton> <PageTitle breadcrumbs={breadcrumbs} /> diff --git a/modern/src/common/components/PositionValue.js b/modern/src/common/components/PositionValue.js index 1162a150..3dd0eb6f 100644 --- a/modern/src/common/components/PositionValue.js +++ b/modern/src/common/components/PositionValue.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Link } from '@material-ui/core'; +import { Link } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { formatAlarm, formatBoolean, formatCoordinate, formatCourse, formatDistance, formatNumber, formatPercentage, formatSpeed, formatTime, diff --git a/modern/src/common/components/RemoveDialog.js b/modern/src/common/components/RemoveDialog.js index a11af4c2..1af4e6a8 100644 --- a/modern/src/common/components/RemoveDialog.js +++ b/modern/src/common/components/RemoveDialog.js @@ -1,6 +1,7 @@ import React from 'react'; -import Button from '@material-ui/core/Button'; -import { Snackbar, makeStyles } from '@material-ui/core'; +import Button from '@mui/material/Button'; +import { Snackbar } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useTranslation } from './LocalizationProvider'; import { useCatch } from '../../reactHelper'; import { snackBarDurationLongMs } from '../util/duration'; diff --git a/modern/src/common/components/SelectField.js b/modern/src/common/components/SelectField.js index 5a9a176b..d3de5c46 100644 --- a/modern/src/common/components/SelectField.js +++ b/modern/src/common/components/SelectField.js @@ -1,12 +1,11 @@ import { FormControl, InputLabel, MenuItem, Select, -} from '@material-ui/core'; +} from '@mui/material'; import React, { useState } from 'react'; import { useEffectAsync } from '../../reactHelper'; const SelectField = ({ margin, - variant, label, multiple, value, @@ -33,9 +32,10 @@ const SelectField = ({ if (items) { return ( - <FormControl margin={margin} variant={variant}> + <FormControl margin={margin} fullWidth> <InputLabel>{label}</InputLabel> <Select + label={label} multiple={multiple} value={value} onChange={onChange} diff --git a/modern/src/common/components/SideNav.js b/modern/src/common/components/SideNav.js index ad7c212a..648059d1 100644 --- a/modern/src/common/components/SideNav.js +++ b/modern/src/common/components/SideNav.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { List, ListItem, ListItemText, ListItemIcon, Divider, ListSubheader, -} from '@material-ui/core'; +} from '@mui/material'; import { Link, useLocation } from 'react-router-dom'; const SideNav = ({ routes }) => { diff --git a/modern/src/common/theme/components.js b/modern/src/common/theme/components.js new file mode 100644 index 00000000..f5f3acaa --- /dev/null +++ b/modern/src/common/theme/components.js @@ -0,0 +1,15 @@ +export default { + MuiFormControl: { + defaultProps: { + size: 'small', + }, + }, + MuiSnackbar: { + defaultProps: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'center', + }, + }, + }, +}; diff --git a/modern/src/common/theme/index.js b/modern/src/common/theme/index.js index 02865c23..0cc999ad 100644 --- a/modern/src/common/theme/index.js +++ b/modern/src/common/theme/index.js @@ -1,12 +1,12 @@ -import { createTheme } from '@material-ui/core/styles'; +import { createTheme } from '@mui/material/styles'; import palette from './palette'; -import overrides from './overrides'; import dimensions from './dimensions'; +import components from './components'; const theme = createTheme({ palette, - overrides, dimensions, + components, }); export default theme; diff --git a/modern/src/common/theme/overrides.js b/modern/src/common/theme/overrides.js deleted file mode 100644 index d1fe844c..00000000 --- a/modern/src/common/theme/overrides.js +++ /dev/null @@ -1,87 +0,0 @@ -import dimensions from './dimensions'; - -export default { - MuiFormControl: { - root: { - marginTop: 5, - marginBottom: 5, - }, - }, - MuiInputLabel: { - filled: { - transform: 'translate(12px, 14px) scale(1)', - '&$shrink': { - transform: 'translate(12px, -14px) scale(0.72)', - }, - }, - }, - MuiFilledInput: { - root: { - height: dimensions.inputHeight, - borderRadius: dimensions.borderRadius, - backgroundColor: 'rgba(0, 0, 0, 0.035)', - }, - input: { - height: dimensions.inputHeight, - borderRadius: dimensions.borderRadius, - paddingTop: '11.5px', - paddingBottom: '11.5px', - boxSizing: 'border-box', - '&:-webkit-autofill': { - WebkitBoxShadow: '0 0 0 100px #eeeeee inset', - }, - }, - underline: { - '&:before': { - borderBottom: 'none', - }, - '&:after': { - borderBottom: 'none', - }, - '&:hover:before': { - borderBottom: 'none', - }, - }, - }, - MuiSelect: { - select: { - borderRadius: dimensions.borderRadius, - '&&:focus': { - borderRadius: dimensions.borderRadius, - }, - }, - }, - MuiButton: { - root: { - height: dimensions.inputHeight, - marginTop: 5, - marginBottom: 5, - '&$disabled': { - opacity: 0.4, - color: undefined, - }, - }, - contained: { - '&$disabled': { - opacity: 0.4, - color: undefined, - backgroundColor: undefined, - }, - }, - }, - MuiFormHelperText: { - root: { - marginBottom: -10, - }, - contained: { - marginLeft: 12, - }, - }, - MuiAutocomplete: { - inputRoot: { - '&.MuiFilledInput-root': { - paddingTop: 0, - }, - }, - }, -}; diff --git a/modern/src/index.js b/modern/src/index.js index cb6710d5..547e39ef 100644 --- a/modern/src/index.js +++ b/modern/src/index.js @@ -3,22 +3,36 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; - -import App from './App'; +import { CssBaseline, ThemeProvider, StyledEngineProvider } from '@mui/material'; import * as serviceWorker from './serviceWorker'; import store from './store'; import { LocalizationProvider } from './common/components/LocalizationProvider'; +import ErrorHandler from './common/components/ErrorHandler'; +import CachingController from './CachingController'; +import SocketController from './SocketController'; +import theme from './common/theme'; +import Navigation from './Navigation'; -const base = window.location.href.indexOf('modern') >= 0 ? '/modern' : null; +const base = window.location.href.indexOf('modern') >= 0 ? '/modern' : '/'; -ReactDOM.render(( - <Provider store={store}> - <LocalizationProvider> - <BrowserRouter basename={base}> - <App /> - </BrowserRouter> - </LocalizationProvider> - </Provider> -), document.getElementById('root')); +ReactDOM.render( + ( + <Provider store={store}> + <LocalizationProvider> + <StyledEngineProvider injectFirst> + <ThemeProvider theme={theme}> + <CssBaseline /> + <BrowserRouter basename={base}> + <SocketController /> + <CachingController /> + <Navigation /> + </BrowserRouter> + <ErrorHandler /> + </ThemeProvider> + </StyledEngineProvider> + </LocalizationProvider> + </Provider> + ), document.getElementById('root'), +); serviceWorker.register(); diff --git a/modern/src/login/LoginLayout.js b/modern/src/login/LoginLayout.js index 4a2bf43a..cfdd837c 100644 --- a/modern/src/login/LoginLayout.js +++ b/modern/src/login/LoginLayout.js @@ -1,6 +1,7 @@ import React from 'react'; -import { useMediaQuery, makeStyles, Paper } from '@material-ui/core'; -import { useTheme } from '@material-ui/core/styles'; +import { useMediaQuery, Paper } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { useTheme } from '@mui/material/styles'; const useStyles = makeStyles((theme) => ({ root: { @@ -15,10 +16,10 @@ const useStyles = makeStyles((theme) => ({ background: theme.palette.primary.main, paddingBottom: theme.spacing(5), width: theme.dimensions.sidebarWidth, - [theme.breakpoints.down('md')]: { + [theme.breakpoints.down('lg')]: { width: theme.dimensions.sidebarWidthTablet, }, - [theme.breakpoints.down('xs')]: { + [theme.breakpoints.down('sm')]: { width: '0px', }, }, @@ -54,12 +55,11 @@ const LoginLayout = ({ children }) => { <> <main className={classes.root}> <div className={classes.sidebar}> - {!useMediaQuery(theme.breakpoints.down('md')) - && ( + {!useMediaQuery(theme.breakpoints.down('lg')) && ( <svg height="64" width="240"> <use xlinkHref="/logo.svg#img" /> </svg> - )} + )} </div> <Paper className={classes.paper}> <form className={classes.form}> diff --git a/modern/src/login/LoginPage.js b/modern/src/login/LoginPage.js index 5b690cdc..cc010427 100644 --- a/modern/src/login/LoginPage.js +++ b/modern/src/login/LoginPage.js @@ -1,12 +1,24 @@ import React, { useState } from 'react'; import { - Grid, useMediaQuery, makeStyles, InputLabel, Select, MenuItem, FormControl, Button, TextField, Link, Snackbar, IconButton, Tooltip, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import CachedIcon from '@material-ui/icons/Cached'; -import { useTheme } from '@material-ui/core/styles'; + Grid, + useMediaQuery, + InputLabel, + Select, + MenuItem, + FormControl, + Button, + TextField, + Link, + Snackbar, + IconButton, + Tooltip, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import CloseIcon from '@mui/icons-material/Close'; +import CachedIcon from '@mui/icons-material/Cached'; +import { useTheme } from '@mui/material/styles'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { sessionActions } from '../store'; import { useLocalization, useTranslation } from '../common/components/LocalizationProvider'; import LoginLayout from './LoginLayout'; @@ -30,7 +42,7 @@ const useStyles = makeStyles((theme) => ({ const LoginPage = () => { const classes = useStyles(); const dispatch = useDispatch(); - const history = useHistory(); + const navigate = useNavigate(); const theme = useTheme(); const t = useTranslation(); @@ -58,7 +70,7 @@ const LoginPage = () => { if (response.ok) { const user = await response.json(); dispatch(sessionActions.updateUser(user)); - history.push('/'); + navigate('/'); } else { throw Error(await response.text()); } @@ -82,7 +94,7 @@ const LoginPage = () => { </IconButton> </Tooltip> <Grid container direction="column" spacing={2}> - {useMediaQuery(theme.breakpoints.down('md')) + {useMediaQuery(theme.breakpoints.down('lg')) && ( <Grid item className={classes.logoContainer}> <svg height="64" width="240"> @@ -103,7 +115,6 @@ const LoginPage = () => { onChange={(e) => setEmail(e.target.value)} onKeyUp={handleSpecialKey} helperText={failed && 'Invalid username or password'} - variant="filled" /> </Grid> <Grid item> @@ -119,7 +130,6 @@ const LoginPage = () => { autoFocus={!!email} onChange={(e) => setPassword(e.target.value)} onKeyUp={handleSpecialKey} - variant="filled" /> </Grid> <Grid item> @@ -136,14 +146,14 @@ const LoginPage = () => { </Grid> <Grid item container spacing={2}> <Grid item> - <Button onClick={() => history.push('/register')} disabled={!registrationEnabled} color="secondary"> + <Button onClick={() => navigate('/register')} disabled={!registrationEnabled} color="secondary"> {t('loginRegister')} </Button> </Grid> <Grid item xs> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('loginLanguage')}</InputLabel> - <Select value={language} onChange={(e) => setLanguage(e.target.value)}> + <Select label={t('loginLanguage')} value={language} onChange={(e) => setLanguage(e.target.value)}> {languageList.map((it) => <MenuItem key={it.code} value={it.code}>{it.name}</MenuItem>)} </Select> </FormControl> @@ -152,7 +162,7 @@ const LoginPage = () => { {emailEnabled && ( <Grid item container justifyContent="flex-end"> <Grid item> - <Link onClick={() => history.push('/reset-password')} className={classes.resetPassword} underline="none">{t('loginReset')}</Link> + <Link onClick={() => navigate('/reset-password')} className={classes.resetPassword} underline="none">{t('loginReset')}</Link> </Grid> </Grid> )} diff --git a/modern/src/login/RegisterPage.js b/modern/src/login/RegisterPage.js index 78728b58..986bf894 100644 --- a/modern/src/login/RegisterPage.js +++ b/modern/src/login/RegisterPage.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; import { - Grid, Button, TextField, Typography, Link, makeStyles, Snackbar, -} from '@material-ui/core'; -import { useHistory } from 'react-router-dom'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; + Grid, Button, TextField, Typography, Link, Snackbar, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import LoginLayout from './LoginLayout'; import { useTranslation } from '../common/components/LocalizationProvider'; import { snackBarDurationShortMs } from '../common/util/duration'; @@ -26,7 +27,7 @@ const useStyles = makeStyles((theme) => ({ const RegisterPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); const [name, setName] = useState(''); @@ -51,7 +52,7 @@ const RegisterPage = () => { <LoginLayout> <Snackbar open={snackbarOpen} - onClose={() => history.push('/login')} + onClose={() => navigate('/login')} autoHideDuration={snackBarDurationShortMs} message={t('loginCreated')} /> @@ -59,7 +60,7 @@ const RegisterPage = () => { <Grid container item> <Grid item> <Typography className={classes.link} color="primary"> - <Link onClick={() => history.push('/login')}> + <Link onClick={() => navigate('/login')}> <ArrowBackIcon /> </Link> </Typography> @@ -80,7 +81,6 @@ const RegisterPage = () => { autoComplete="name" autoFocus onChange={(event) => setName(event.target.value)} - variant="filled" /> </Grid> <Grid item> @@ -93,7 +93,6 @@ const RegisterPage = () => { value={email} autoComplete="email" onChange={(event) => setEmail(event.target.value)} - variant="filled" /> </Grid> <Grid item> @@ -106,7 +105,6 @@ const RegisterPage = () => { type="password" autoComplete="current-password" onChange={(event) => setPassword(event.target.value)} - variant="filled" /> </Grid> <Grid item> diff --git a/modern/src/login/ResetPasswordPage.js b/modern/src/login/ResetPasswordPage.js index 93e154e3..c3e91fe6 100644 --- a/modern/src/login/ResetPasswordPage.js +++ b/modern/src/login/ResetPasswordPage.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; import { - Grid, Button, TextField, Typography, Link, makeStyles, Snackbar, -} from '@material-ui/core'; -import { useHistory } from 'react-router-dom'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; + Grid, Button, TextField, Typography, Link, Snackbar, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import LoginLayout from './LoginLayout'; import { useTranslation } from '../common/components/LocalizationProvider'; import useQuery from '../common/util/useQuery'; @@ -27,7 +28,7 @@ const useStyles = makeStyles((theme) => ({ const ResetPasswordPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); const query = useQuery(); @@ -62,7 +63,7 @@ const ResetPasswordPage = () => { <LoginLayout> <Snackbar open={snackbarOpen} - onClose={() => history.push('/login')} + onClose={() => navigate('/login')} autoHideDuration={snackBarDurationShortMs} message={!token ? t('loginResetSuccess') : t('loginUpdateSuccess')} /> @@ -70,7 +71,7 @@ const ResetPasswordPage = () => { <Grid container item> <Grid item> <Typography className={classes.link} color="primary"> - <Link onClick={() => history.push('/login')}> + <Link onClick={() => navigate('/login')}> <ArrowBackIcon /> </Link> </Typography> @@ -93,7 +94,6 @@ const ResetPasswordPage = () => { value={email} autoComplete="email" onChange={(event) => setEmail(event.target.value)} - variant="filled" /> </Grid> ) @@ -108,7 +108,6 @@ const ResetPasswordPage = () => { type="password" autoComplete="current-password" onChange={(event) => setPassword(event.target.value)} - variant="filled" /> </Grid> )} diff --git a/modern/src/main/DevicesList.js b/modern/src/main/DevicesList.js index 6ff08c6a..06b75715 100644 --- a/modern/src/main/DevicesList.js +++ b/modern/src/main/DevicesList.js @@ -1,27 +1,26 @@ import React, { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { makeStyles } from '@material-ui/core/styles'; -import { IconButton, Tooltip } from '@material-ui/core'; -import Avatar from '@material-ui/core/Avatar'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemAvatar from '@material-ui/core/ListItemAvatar'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; +import makeStyles from '@mui/styles/makeStyles'; +import { IconButton, Tooltip } from '@mui/material'; +import Avatar from '@mui/material/Avatar'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import ListItemText from '@mui/material/ListItemText'; import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; -import BatteryFullIcon from '@material-ui/icons/BatteryFull'; -import BatteryChargingFullIcon from '@material-ui/icons/BatteryChargingFull'; -import Battery60Icon from '@material-ui/icons/Battery60'; -import BatteryCharging60Icon from '@material-ui/icons/BatteryCharging60'; -import Battery20Icon from '@material-ui/icons/Battery20'; -import BatteryCharging20Icon from '@material-ui/icons/BatteryCharging20'; -import FlashOnIcon from '@material-ui/icons/FlashOn'; -import FlashOffIcon from '@material-ui/icons/FlashOff'; -import ErrorIcon from '@material-ui/icons/Error'; +import BatteryFullIcon from '@mui/icons-material/BatteryFull'; +import BatteryChargingFullIcon from '@mui/icons-material/BatteryChargingFull'; +import Battery60Icon from '@mui/icons-material/Battery60'; +import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60'; +import Battery20Icon from '@mui/icons-material/Battery20'; +import BatteryCharging20Icon from '@mui/icons-material/BatteryCharging20'; +import FlashOnIcon from '@mui/icons-material/FlashOn'; +import FlashOffIcon from '@mui/icons-material/FlashOff'; +import ErrorIcon from '@mui/icons-material/Error'; import { devicesActions } from '../store'; -import EditCollectionView from '../settings/components/EditCollectionView'; import { useEffectAsync } from '../reactHelper'; import { formatAlarm, formatBoolean, formatPercentage, formatStatus, getStatusColor, @@ -139,7 +138,7 @@ const DeviceRow = ({ data, index, style }) => { ); }; -const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { +const DevicesList = ({ filter }) => { const classes = useStyles(); const dispatch = useDispatch(); const listInnerEl = useRef(null); @@ -167,7 +166,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, []); return ( <AutoSizer className={classes.list}> @@ -177,7 +176,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { width={width} height={height} itemCount={filteredItems.length} - itemData={{ items: filteredItems, onMenuClick }} + itemData={{ items: filteredItems }} itemSize={72} overscanCount={10} innerRef={listInnerEl} @@ -190,8 +189,4 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => { ); }; -const DevicesList = ({ filter }) => ( - <EditCollectionView content={DeviceView} editPath="/settings/device" endpoint="devices" disableAdd filter={filter} /> -); - export default DevicesList; diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js index 61c10d81..059d7043 100644 --- a/modern/src/main/MainPage.js +++ b/modern/src/main/MainPage.js @@ -1,15 +1,17 @@ import React, { useState, useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { - makeStyles, Paper, Toolbar, TextField, IconButton, Button, -} from '@material-ui/core'; + Paper, Toolbar, TextField, IconButton, Button, +} from '@mui/material'; -import { useTheme } from '@material-ui/core/styles'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; -import AddIcon from '@material-ui/icons/Add'; -import CloseIcon from '@material-ui/icons/Close'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import ListIcon from '@material-ui/icons/ViewList'; +import makeStyles from '@mui/styles/makeStyles'; + +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ListIcon from '@mui/icons-material/ViewList'; import { useDispatch, useSelector } from 'react-redux'; import DevicesList from './DevicesList'; @@ -46,7 +48,7 @@ const useStyles = makeStyles((theme) => ({ bottom: theme.dimensions.bottomBarHeight, transition: 'transform .5s ease', backgroundColor: 'white', - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { width: '100%', margin: 0, }, @@ -54,7 +56,7 @@ const useStyles = makeStyles((theme) => ({ sidebarCollapsed: { transform: `translateX(-${theme.dimensions.drawerWidthDesktop})`, marginLeft: 0, - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { transform: 'translateX(-100vw)', }, }, @@ -75,7 +77,7 @@ const useStyles = makeStyles((theme) => ({ left: `calc(50% + ${theme.dimensions.drawerWidthDesktop} / 2)`, bottom: theme.spacing(3), }, - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { left: '50%', bottom: theme.spacing(3) + theme.dimensions.bottomBarHeight, }, @@ -87,7 +89,7 @@ const useStyles = makeStyles((theme) => ({ top: theme.spacing(3), borderRadius: '0px', minWidth: 0, - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { left: 0, }, }, @@ -115,14 +117,14 @@ const useStyles = makeStyles((theme) => ({ const MainPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const dispatch = useDispatch(); const theme = useTheme(); const t = useTranslation(); const deviceReadonly = useDeviceReadonly(); const desktop = useMediaQuery(theme.breakpoints.up('md')); - const phone = useMediaQuery(theme.breakpoints.down('xs')); + const phone = useMediaQuery(theme.breakpoints.down('sm')); const [mapLiveRoutes] = usePersistedState('mapLiveRoutes', false); @@ -176,9 +178,8 @@ const MainPage = () => { autoComplete="searchKeyword" onChange={(event) => setSearchKeyword(event.target.value)} placeholder={t('sharedSearchDevices')} - variant="filled" /> - <IconButton onClick={() => history.push('/settings/device')} disabled={deviceReadonly}> + <IconButton onClick={() => navigate('/settings/device')} disabled={deviceReadonly}> <AddIcon /> </IconButton> {desktop && ( diff --git a/modern/src/main/StatusCard.js b/modern/src/main/StatusCard.js index b470890e..ec33efa2 100644 --- a/modern/src/main/StatusCard.js +++ b/modern/src/main/StatusCard.js @@ -1,15 +1,27 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; import { - makeStyles, Card, CardContent, Typography, CardActions, CardHeader, IconButton, Avatar, Table, TableBody, TableRow, TableCell, TableContainer, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import PostAddIcon from '@material-ui/icons/PostAdd'; -import ReplayIcon from '@material-ui/icons/Replay'; -import PublishIcon from '@material-ui/icons/Publish'; -import EditIcon from '@material-ui/icons/Edit'; -import DeleteIcon from '@material-ui/icons/Delete'; + Card, + CardContent, + Typography, + CardActions, + CardHeader, + IconButton, + Avatar, + Table, + TableBody, + TableRow, + TableCell, + TableContainer, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import CloseIcon from '@mui/icons-material/Close'; +import PostAddIcon from '@mui/icons-material/PostAdd'; +import ReplayIcon from '@mui/icons-material/Replay'; +import PublishIcon from '@mui/icons-material/Publish'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; import { useTranslation } from '../common/components/LocalizationProvider'; import { formatStatus } from '../common/util/formatter'; @@ -19,6 +31,8 @@ import dimensions from '../common/theme/dimensions'; import { useDeviceReadonly, useReadonly } from '../common/util/permissions'; import usePersistedState from '../common/util/usePersistedState'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; +import { devicesActions } from '../store'; +import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ card: { @@ -63,7 +77,8 @@ const StatusRow = ({ name, content }) => { const StatusCard = ({ deviceId, onClose }) => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); + const dispatch = useDispatch(); const t = useTranslation(); const readonly = useReadonly(); @@ -75,7 +90,19 @@ const StatusCard = ({ deviceId, onClose }) => { const positionAttributes = usePositionAttributes(t); const [positionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']); - const [removeDialogShown, setRemoveDialogShown] = useState(false); + const [removing, setRemoving] = useState(false); + + const handleRemove = useCatch(async (removed) => { + if (removed) { + const response = await fetch('/api/devices'); + if (response.ok) { + dispatch(devicesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); + } + } + setRemoving(false); + }); return ( <> @@ -119,29 +146,46 @@ const StatusCard = ({ deviceId, onClose }) => { </CardContent> )} <CardActions classes={{ root: classes.actions }} disableSpacing> - <IconButton onClick={() => history.push(`/position/${position.id}`)} disabled={!position} color="secondary"> + <IconButton + color="secondary" + onClick={() => navigate(`/position/${position.id}`)} + disabled={!position} + > <PostAddIcon /> </IconButton> - <IconButton onClick={() => history.push('/replay')} disabled={!position}> + <IconButton + onClick={() => navigate('/replay')} + disabled={!position} + > <ReplayIcon /> </IconButton> - <IconButton onClick={() => history.push(`/settings/command-send/${deviceId}`)} disabled={readonly}> + <IconButton + onClick={() => navigate(`/settings/command-send/${deviceId}`)} + disabled={readonly} + > <PublishIcon /> </IconButton> - <IconButton onClick={() => history.push(`/settings/device/${deviceId}`)} disabled={deviceReadonly}> + <IconButton + onClick={() => navigate(`/settings/device/${deviceId}`)} + disabled={deviceReadonly} + > <EditIcon /> </IconButton> - <IconButton onClick={() => setRemoveDialogShown(true)} disabled={deviceReadonly} className={classes.negative}> + <IconButton + onClick={() => setRemoving(true)} + disabled={deviceReadonly} + className={classes.negative} + > <DeleteIcon /> </IconButton> </CardActions> </Card> )} <RemoveDialog - open={removeDialogShown} + open={removing} endpoint="devices" itemId={deviceId} - onResult={() => setRemoveDialogShown(false)} + onResult={(removed) => handleRemove(removed)} /> </> ); diff --git a/modern/src/map/MapGeofenceEdit.js b/modern/src/map/MapGeofenceEdit.js index c64eb736..0b5062d8 100644 --- a/modern/src/map/MapGeofenceEdit.js +++ b/modern/src/map/MapGeofenceEdit.js @@ -5,7 +5,7 @@ import theme from '@mapbox/mapbox-gl-draw/src/lib/theme'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { map } from './core/Map'; import { geofenceToFeature, geometryToArea } from './core/mapUtil'; import { errorsActions, geofencesActions } from '../store'; @@ -36,7 +36,7 @@ const draw = new MapboxDraw({ const MapGeofenceEdit = () => { const dispatch = useDispatch(); - const history = useHistory(); + const navigate = useNavigate(); const geofences = useSelector((state) => state.geofences.items); @@ -69,7 +69,7 @@ const MapGeofenceEdit = () => { }); if (response.ok) { const item = await response.json(); - history.push(`/settings/geofence/${item.id}`); + navigate(`/settings/geofence/${item.id}`); } else { throw Error(await response.text()); } @@ -80,7 +80,7 @@ const MapGeofenceEdit = () => { map.on('draw.create', listener); return () => map.off('draw.create', listener); - }, [dispatch, history]); + }, [dispatch, navigate]); useEffect(() => { const listener = async (event) => { diff --git a/modern/src/other/EventPage.js b/modern/src/other/EventPage.js index 8174de5a..f4427ca9 100644 --- a/modern/src/other/EventPage.js +++ b/modern/src/other/EventPage.js @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { - makeStyles, Typography, AppBar, Toolbar, IconButton, -} from '@material-ui/core'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import { useHistory, useParams } from 'react-router-dom'; -import ContainerDimensions from 'react-container-dimensions'; + Typography, AppBar, Toolbar, IconButton, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { useNavigate, useParams } from 'react-router-dom'; import { useEffectAsync } from '../reactHelper'; import { useTranslation } from '../common/components/LocalizationProvider'; import Map from '../map/core/Map'; @@ -24,7 +24,7 @@ const useStyles = makeStyles(() => ({ const EventPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); const { id } = useParams(); @@ -61,18 +61,16 @@ const EventPage = () => { <div className={classes.root}> <AppBar color="inherit" position="static"> <Toolbar> - <IconButton color="inherit" edge="start" onClick={() => history.push('/')}> + <IconButton color="inherit" edge="start" onClick={() => navigate('/')}> <ArrowBackIcon /> </IconButton> <Typography variant="h6">{t('positionEvent')}</Typography> </Toolbar> </AppBar> <div className={classes.mapContainer}> - <ContainerDimensions> - <Map> - {position && <MapPositions positions={[position]} />} - </Map> - </ContainerDimensions> + <Map> + {position && <MapPositions positions={[position]} />} + </Map> </div> </div> ); diff --git a/modern/src/other/GeofencesList.js b/modern/src/other/GeofencesList.js index d0d0853f..7521de80 100644 --- a/modern/src/other/GeofencesList.js +++ b/modern/src/other/GeofencesList.js @@ -1,16 +1,14 @@ import React, { Fragment } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { makeStyles } from '@material-ui/core/styles'; -import Divider from '@material-ui/core/Divider'; -import IconButton from '@material-ui/core/IconButton'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; +import makeStyles from '@mui/styles/makeStyles'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; -import { devicesActions } from '../store'; -import EditCollectionView from '../settings/components/EditCollectionView'; +import { devicesActions, geofencesActions } from '../store'; +import CollectionActions from '../settings/components/CollectionActions'; +import { useCatchCallback } from '../reactHelper'; const useStyles = makeStyles(() => ({ list: { @@ -24,23 +22,28 @@ const useStyles = makeStyles(() => ({ }, })); -const GeofenceView = ({ onMenuClick }) => { +const GeofencesList = () => { const classes = useStyles(); const dispatch = useDispatch(); const items = useSelector((state) => state.geofences.items); + const refreshGeofences = useCatchCallback(async () => { + const response = await fetch('/api/geofences'); + if (response.ok) { + dispatch(geofencesActions.refresh(await response.json())); + } else { + throw Error(await response.text()); + } + }, [dispatch]); + return ( <List className={classes.list}> {Object.values(items).map((item, index, list) => ( <Fragment key={item.id}> <ListItem button key={item.id} onClick={() => dispatch(devicesActions.select(item.id))}> <ListItemText primary={item.name} /> - <ListItemSecondaryAction> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </ListItemSecondaryAction> + <CollectionActions itemId={item.id} editPath="/settings/geofence" endpoint="geofences" setTimestamp={refreshGeofences} /> </ListItem> {index < list.length - 1 ? <Divider /> : null} </Fragment> @@ -49,8 +52,4 @@ const GeofenceView = ({ onMenuClick }) => { ); }; -const GeofencesList = () => ( - <EditCollectionView content={GeofenceView} editPath="/settings/geofence" endpoint="geofences" disableAdd /> -); - export default GeofencesList; diff --git a/modern/src/other/GeofencesPage.js b/modern/src/other/GeofencesPage.js index 1e866de6..6706ec98 100644 --- a/modern/src/other/GeofencesPage.js +++ b/modern/src/other/GeofencesPage.js @@ -1,12 +1,12 @@ import React from 'react'; import { - Divider, makeStyles, Typography, IconButton, useMediaQuery, -} from '@material-ui/core'; -import { useTheme } from '@material-ui/core/styles'; -import Drawer from '@material-ui/core/Drawer'; -import ContainerDimensions from 'react-container-dimensions'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import { useHistory } from 'react-router-dom'; + Divider, Typography, IconButton, useMediaQuery, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { useTheme } from '@mui/material/styles'; +import Drawer from '@mui/material/Drawer'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { useNavigate } from 'react-router-dom'; import Map from '../map/core/Map'; import MapCurrentLocation from '../map/MapCurrentLocation'; import MapGeofenceEdit from '../map/MapGeofenceEdit'; @@ -25,7 +25,7 @@ const useStyles = makeStyles((theme) => ({ overflow: 'hidden', display: 'flex', flexDirection: 'row', - [theme.breakpoints.down('xs')]: { + [theme.breakpoints.down('sm')]: { flexDirection: 'column-reverse', }, }, @@ -34,7 +34,7 @@ const useStyles = makeStyles((theme) => ({ [theme.breakpoints.up('sm')]: { width: dimensions.drawerWidthTablet, }, - [theme.breakpoints.down('xs')]: { + [theme.breakpoints.down('sm')]: { height: dimensions.drawerHeightPhone, }, }, @@ -52,10 +52,10 @@ const useStyles = makeStyles((theme) => ({ const GeofencesPage = () => { const theme = useTheme(); const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); - const isPhone = useMediaQuery(theme.breakpoints.down('xs')); + const isPhone = useMediaQuery(theme.breakpoints.down('sm')); return ( <div className={classes.root}> @@ -66,7 +66,7 @@ const GeofencesPage = () => { classes={{ paper: classes.drawerPaper }} > <div className={classes.drawerHeader}> - <IconButton onClick={() => history.goBack()}> + <IconButton onClick={() => navigate(-1)}> <ArrowBackIcon /> </IconButton> <Typography variant="h6" color="inherit" noWrap> @@ -77,12 +77,10 @@ const GeofencesPage = () => { <GeofencesList /> </Drawer> <div className={classes.mapContainer}> - <ContainerDimensions> - <Map> - <MapCurrentLocation /> - <MapGeofenceEdit /> - </Map> - </ContainerDimensions> + <Map> + <MapCurrentLocation /> + <MapGeofenceEdit /> + </Map> </div> </div> </div> diff --git a/modern/src/other/PositionPage.js b/modern/src/other/PositionPage.js index 76bb560f..61c40902 100644 --- a/modern/src/other/PositionPage.js +++ b/modern/src/other/PositionPage.js @@ -2,10 +2,21 @@ import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import { - makeStyles, Typography, Container, Paper, AppBar, Toolbar, IconButton, Table, TableHead, TableRow, TableCell, TableBody, -} from '@material-ui/core'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import { useHistory, useParams } from 'react-router-dom'; + Typography, + Container, + Paper, + AppBar, + Toolbar, + IconButton, + Table, + TableHead, + TableRow, + TableCell, + TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { useNavigate, useParams } from 'react-router-dom'; import { useEffectAsync } from '../reactHelper'; import { prefixString } from '../common/util/stringUtils'; import { useTranslation } from '../common/components/LocalizationProvider'; @@ -26,7 +37,7 @@ const useStyles = makeStyles((theme) => ({ const PositionPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const t = useTranslation(); const { id } = useParams(); @@ -63,7 +74,7 @@ const PositionPage = () => { <div className={classes.root}> <AppBar position="sticky" color="inherit"> <Toolbar> - <IconButton color="inherit" edge="start" onClick={() => history.goBack()}> + <IconButton color="inherit" edge="start" onClick={() => navigate(-1)}> <ArrowBackIcon /> </IconButton> <Typography variant="h6"> diff --git a/modern/src/other/ReplayPage.js b/modern/src/other/ReplayPage.js index 5e759d7e..1c82c913 100644 --- a/modern/src/other/ReplayPage.js +++ b/modern/src/other/ReplayPage.js @@ -2,15 +2,16 @@ import React, { useState, useEffect, useRef, useCallback, } from 'react'; import { - Grid, IconButton, makeStyles, Paper, Slider, Toolbar, Tooltip, Typography, -} from '@material-ui/core'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import SettingsIcon from '@material-ui/icons/Settings'; -import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import PauseIcon from '@material-ui/icons/Pause'; -import FastForwardIcon from '@material-ui/icons/FastForward'; -import FastRewindIcon from '@material-ui/icons/FastRewind'; -import { useHistory } from 'react-router-dom'; + Grid, IconButton, Paper, Slider, Toolbar, Tooltip, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import SettingsIcon from '@mui/icons-material/Settings'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import PauseIcon from '@mui/icons-material/Pause'; +import FastForwardIcon from '@mui/icons-material/FastForward'; +import FastRewindIcon from '@mui/icons-material/FastRewind'; +import { useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; import Map from '../map/core/Map'; import MapReplayPath from '../map/MapReplayPath'; @@ -33,7 +34,7 @@ const useStyles = makeStyles((theme) => ({ top: 0, margin: theme.spacing(1.5), width: theme.dimensions.drawerWidthDesktop, - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { width: '100%', margin: 0, }, @@ -60,7 +61,7 @@ const useStyles = makeStyles((theme) => ({ display: 'flex', flexDirection: 'column', padding: theme.spacing(2), - [theme.breakpoints.down('sm')]: { + [theme.breakpoints.down('md')]: { margin: theme.spacing(1), }, [theme.breakpoints.up('md')]: { @@ -78,7 +79,7 @@ const TimeLabel = ({ children, open, value }) => ( const ReplayPage = () => { const t = useTranslation(); const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const timerRef = useRef(); const defaultDeviceId = useSelector((state) => state.devices.selectedId); @@ -100,8 +101,8 @@ const ReplayPage = () => { }); const onClick = useCallback((positionId) => { - history.push(`/position/${positionId}`); - }, [history]); + navigate(`/position/${positionId}`); + }, [navigate]); useEffect(() => { if (playing && positions.length > 0) { @@ -146,7 +147,7 @@ const ReplayPage = () => { <div className={classes.sidebar}> <Paper elevation={3} square> <Toolbar> - <IconButton onClick={() => history.push('/')}> + <IconButton onClick={() => navigate('/')}> <ArrowBackIcon /> </IconButton> <Typography variant="h6" className={classes.title}>{t('reportReplay')}</Typography> diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js index bb71fa95..becdfb57 100644 --- a/modern/src/reports/ChartReportPage.js +++ b/modern/src/reports/ChartReportPage.js @@ -1,7 +1,8 @@ import React, { useState } from 'react'; import { - FormControl, InputLabel, Select, MenuItem, makeStyles, -} from '@material-ui/core'; + FormControl, InputLabel, Select, MenuItem, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; @@ -58,9 +59,9 @@ const ChartReportPage = () => { <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportChart']}> <ReportFilter handleSubmit={handleSubmit} showOnly> <div className={filterClasses.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('reportChartType')}</InputLabel> - <Select value={type} onChange={(e) => setType(e.target.value)}> + <Select label={t('reportChartType')} value={type} onChange={(e) => setType(e.target.value)}> {Object.keys(positionAttributes).filter((key) => positionAttributes[key].type === 'number').map((key) => ( <MenuItem key={key} value={key}>{positionAttributes[key].name}</MenuItem> ))} diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js index bd0ad68b..8d71d50b 100644 --- a/modern/src/reports/EventReportPage.js +++ b/modern/src/reports/EventReportPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { FormControl, InputLabel, Select, MenuItem, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, -} from '@material-ui/core'; +} from '@mui/material'; import { useSelector } from 'react-redux'; import { formatDate } from '../common/util/formatter'; import ReportFilter, { useFilterStyles } from './components/ReportFilter'; @@ -99,9 +99,10 @@ const EventReportPage = () => { <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportEvents']}> <ReportFilter handleSubmit={handleSubmit}> <div className={classes.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('reportEventTypes')}</InputLabel> <Select + label={t('reportEventTypes')} value={eventTypes} onChange={(event, child) => { let values = event.target.value; diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index b67833f9..3f89888f 100644 --- a/modern/src/reports/RouteReportPage.js +++ b/modern/src/reports/RouteReportPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, -} from '@material-ui/core'; +} from '@mui/material'; import ReportFilter from './components/ReportFilter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; diff --git a/modern/src/reports/StatisticsPage.js b/modern/src/reports/StatisticsPage.js index 9ed830eb..b9b14902 100644 --- a/modern/src/reports/StatisticsPage.js +++ b/modern/src/reports/StatisticsPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, -} from '@material-ui/core'; +} from '@mui/material'; import { formatDate } from '../common/util/formatter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js index 5b5fdc29..587e1dd4 100644 --- a/modern/src/reports/StopReportPage.js +++ b/modern/src/reports/StopReportPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, -} from '@material-ui/core'; +} from '@mui/material'; import { formatDistance, formatHours, formatDate, formatVolume, } from '../common/util/formatter'; diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js index 12f39ac8..d58f7568 100644 --- a/modern/src/reports/SummaryReportPage.js +++ b/modern/src/reports/SummaryReportPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { FormControl, InputLabel, Select, MenuItem, TableContainer, Table, TableHead, TableRow, TableBody, TableCell, -} from '@material-ui/core'; +} from '@mui/material'; import { formatDistance, formatHours, formatDate, formatSpeed, formatVolume, } from '../common/util/formatter'; @@ -81,9 +81,9 @@ const SummaryReportPage = () => { <PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportSummary']}> <ReportFilter handleSubmit={handleSubmit}> <div className={classes.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('sharedType')}</InputLabel> - <Select value={daily} onChange={(e) => setDaily(e.target.value)}> + <Select label={t('sharedType')} value={daily} onChange={(e) => setDaily(e.target.value)}> <MenuItem value={false}>{t('reportSummary')}</MenuItem> <MenuItem value>{t('reportDaily')}</MenuItem> </Select> diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js index 5662e18a..8e03931b 100644 --- a/modern/src/reports/TripReportPage.js +++ b/modern/src/reports/TripReportPage.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, -} from '@material-ui/core'; +} from '@mui/material'; import { formatDistance, formatSpeed, formatHours, formatDate, formatVolume, } from '../common/util/formatter'; diff --git a/modern/src/reports/components/ColumnSelect.js b/modern/src/reports/components/ColumnSelect.js index d7306c58..8a79108e 100644 --- a/modern/src/reports/components/ColumnSelect.js +++ b/modern/src/reports/components/ColumnSelect.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormControl, InputLabel, MenuItem, Select, -} from '@material-ui/core'; +} from '@mui/material'; import { useTranslation } from '../../common/components/LocalizationProvider'; import { useFilterStyles } from './ReportFilter'; @@ -13,9 +13,10 @@ const ColumnSelect = ({ return ( <div className={classes.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('sharedColumns')}</InputLabel> <Select + label={t('sharedColumns')} value={columns} onChange={(e) => setColumns(e.target.value)} renderValue={(it) => it.length} diff --git a/modern/src/reports/components/ReportFilter.js b/modern/src/reports/components/ReportFilter.js index 350bbe6d..42fcaa9a 100644 --- a/modern/src/reports/components/ReportFilter.js +++ b/modern/src/reports/components/ReportFilter.js @@ -1,7 +1,8 @@ import React, { useState } from 'react'; import { - FormControl, InputLabel, Select, MenuItem, Button, TextField, Typography, makeStyles, -} from '@material-ui/core'; + FormControl, InputLabel, Select, MenuItem, Button, TextField, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useSelector } from 'react-redux'; import moment from 'moment'; import { useTranslation } from '../../common/components/LocalizationProvider'; @@ -89,9 +90,9 @@ const ReportFilter = ({ <div className={classes.filter}> {!ignoreDevice && ( <div className={classes.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('reportDevice')}</InputLabel> - <Select value={deviceId || ''} onChange={(e) => setDeviceId(e.target.value)}> + <Select label={t('reportDevice')} value={deviceId || ''} onChange={(e) => setDeviceId(e.target.value)}> {Object.values(devices).map((device) => ( <MenuItem key={device.id} value={device.id}>{device.name}</MenuItem> ))} @@ -100,9 +101,9 @@ const ReportFilter = ({ </div> )} <div className={classes.item}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('reportPeriod')}</InputLabel> - <Select value={period} onChange={(e) => setPeriod(e.target.value)}> + <Select label={t('reportPeriod')} value={period} onChange={(e) => setPeriod(e.target.value)}> <MenuItem value="today">{t('reportToday')}</MenuItem> <MenuItem value="yesterday">{t('reportYesterday')}</MenuItem> <MenuItem value="thisWeek">{t('reportThisWeek')}</MenuItem> @@ -116,7 +117,6 @@ const ReportFilter = ({ {period === 'custom' && ( <div className={classes.item}> <TextField - variant="filled" label={t('reportFrom')} type="datetime-local" value={from.format(moment.HTML5_FMT.DATETIME_LOCAL)} @@ -128,7 +128,6 @@ const ReportFilter = ({ {period === 'custom' && ( <div className={classes.item}> <TextField - variant="filled" label={t('reportTo')} type="datetime-local" value={to.format(moment.HTML5_FMT.DATETIME_LOCAL)} diff --git a/modern/src/reports/components/ReportsMenu.js b/modern/src/reports/components/ReportsMenu.js index 8e973562..afe9de79 100644 --- a/modern/src/reports/components/ReportsMenu.js +++ b/modern/src/reports/components/ReportsMenu.js @@ -1,14 +1,14 @@ import React from 'react'; import { Divider, List, ListItem, ListItemIcon, ListItemText, -} from '@material-ui/core'; -import TimelineIcon from '@material-ui/icons/Timeline'; -import PauseCircleFilledIcon from '@material-ui/icons/PauseCircleFilled'; -import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled'; -import NotificationsActiveIcon from '@material-ui/icons/NotificationsActive'; -import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted'; -import TrendingUpIcon from '@material-ui/icons/TrendingUp'; -import BarChartIcon from '@material-ui/icons/BarChart'; +} from '@mui/material'; +import TimelineIcon from '@mui/icons-material/Timeline'; +import PauseCircleFilledIcon from '@mui/icons-material/PauseCircleFilled'; +import PlayCircleFilledIcon from '@mui/icons-material/PlayCircleFilled'; +import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'; +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; +import TrendingUpIcon from '@mui/icons-material/TrendingUp'; +import BarChartIcon from '@mui/icons-material/BarChart'; import { Link, useLocation } from 'react-router-dom'; import { useTranslation } from '../../common/components/LocalizationProvider'; import { useAdministrator } from '../../common/util/permissions'; diff --git a/modern/src/settings/AccumulatorsPage.js b/modern/src/settings/AccumulatorsPage.js index b22fe02e..ce2d18a2 100644 --- a/modern/src/settings/AccumulatorsPage.js +++ b/modern/src/settings/AccumulatorsPage.js @@ -1,10 +1,18 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Container, TextField, FormControl, Button, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + Container, + TextField, + FormControl, + Button, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; @@ -27,7 +35,7 @@ const useStyles = makeStyles((theme) => ({ })); const AccumulatorsPage = () => { - const history = useHistory(); + const navigate = useNavigate(); const classes = useStyles(); const t = useTranslation(); @@ -54,7 +62,7 @@ const AccumulatorsPage = () => { }); if (response.ok) { - history.goBack(); + navigate(-1); } else { throw Error(await response.text()); } @@ -77,7 +85,6 @@ const AccumulatorsPage = () => { value={item.hours} onChange={(event) => setItem({ ...item, hours: Number(event.target.value) })} label={t('positionHours')} - variant="filled" /> <TextField margin="normal" @@ -85,7 +92,6 @@ const AccumulatorsPage = () => { value={item.totalDistance} onChange={(event) => setItem({ ...item, totalDistance: Number(event.target.value) })} label={t('deviceTotalDistance')} - variant="filled" /> </AccordionDetails> </Accordion> @@ -95,7 +101,7 @@ const AccumulatorsPage = () => { type="button" color="primary" variant="outlined" - onClick={() => history.goBack()} + onClick={() => navigate(-1)} > {t('sharedCancel')} </Button> diff --git a/modern/src/settings/CalendarPage.js b/modern/src/settings/CalendarPage.js index 154b3f11..1d967a35 100644 --- a/modern/src/settings/CalendarPage.js +++ b/modern/src/settings/CalendarPage.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, AccordionSummary, AccordionDetails, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { DropzoneArea } from 'material-ui-dropzone'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; @@ -60,7 +61,6 @@ const CalendarPage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> <DropzoneArea filesLimit={1} diff --git a/modern/src/settings/CalendarsPage.js b/modern/src/settings/CalendarsPage.js index 06697647..a3ff51d5 100644 --- a/modern/src/settings/CalendarsPage.js +++ b/modern/src/settings/CalendarsPage.js @@ -1,13 +1,14 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +17,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const CalendarsView = ({ updateTimestamp, onMenuClick }) => { +const CalendarsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,38 +31,33 @@ const CalendarsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedCalendars']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/calendar" endpoint="calendars" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/calendar" /> + </PageLayout> ); }; -const CalendarsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedCalendars']}> - <EditCollectionView content={CalendarsView} editPath="/settings/calendar" endpoint="calendars" /> - </PageLayout> -); - export default CalendarsPage; diff --git a/modern/src/settings/CommandPage.js b/modern/src/settings/CommandPage.js index 4785021b..5c306e4c 100644 --- a/modern/src/settings/CommandPage.js +++ b/modern/src/settings/CommandPage.js @@ -1,8 +1,9 @@ import React, { useState } from 'react'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, TextField, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, AccordionSummary, AccordionDetails, Typography, TextField, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import { useTranslation } from '../common/components/LocalizationProvider'; import BaseCommandView from './components/BaseCommandView'; @@ -44,7 +45,6 @@ const CommandPage = () => { value={item.description || ''} onChange={(event) => setItem({ ...item, description: event.target.value })} label={t('sharedDescription')} - variant="filled" /> <BaseCommandView item={item} setItem={setItem} /> </AccordionDetails> diff --git a/modern/src/settings/CommandSendPage.js b/modern/src/settings/CommandSendPage.js index cc0dae63..29c9df46 100644 --- a/modern/src/settings/CommandSendPage.js +++ b/modern/src/settings/CommandSendPage.js @@ -1,9 +1,16 @@ import React, { useState } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Container, Button, FormControl, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + Container, + Button, + FormControl, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useTranslation } from '../common/components/LocalizationProvider'; import BaseCommandView from './components/BaseCommandView'; import SelectField from '../common/components/SelectField'; @@ -28,7 +35,7 @@ const useStyles = makeStyles((theme) => ({ })); const CommandSendPage = () => { - const history = useHistory(); + const navigate = useNavigate(); const classes = useStyles(); const t = useTranslation(); @@ -59,7 +66,7 @@ const CommandSendPage = () => { }); if (response.ok) { - history.goBack(); + navigate(-1); } else { throw Error(await response.text()); } @@ -85,7 +92,6 @@ const CommandSendPage = () => { endpoint={`/api/commands/send?deviceId=${deviceId}`} titleGetter={(it) => it.description} label={t('sharedSavedCommand')} - variant="filled" /> {!savedId && ( <BaseCommandView item={item} setItem={setItem} /> @@ -98,7 +104,7 @@ const CommandSendPage = () => { type="button" color="primary" variant="outlined" - onClick={() => history.goBack()} + onClick={() => navigate(-1)} > {t('sharedCancel')} </Button> diff --git a/modern/src/settings/CommandsPage.js b/modern/src/settings/CommandsPage.js index 1b09a8bd..dd1559d9 100644 --- a/modern/src/settings/CommandsPage.js +++ b/modern/src/settings/CommandsPage.js @@ -1,15 +1,16 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import { formatBoolean } from '../common/util/formatter'; import { prefixString } from '../common/util/stringUtils'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -18,10 +19,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const CommandsView = ({ updateTimestamp, onMenuClick }) => { +const CommandsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -31,42 +33,37 @@ const CommandsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedDescription')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - <TableCell>{t('commandSendSms')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.description}</TableCell> - <TableCell>{t(prefixString('command', item.type))}</TableCell> - <TableCell>{formatBoolean(item.textChannel, t)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedSavedCommands']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedDescription')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + <TableCell>{t('commandSendSms')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/command" endpoint="commands" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.description}</TableCell> + <TableCell>{t(prefixString('command', item.type))}</TableCell> + <TableCell>{formatBoolean(item.textChannel, t)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/command" /> + </PageLayout> ); }; -const CommandsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedSavedCommands']}> - <EditCollectionView content={CommandsView} editPath="/settings/command" endpoint="commands" /> - </PageLayout> -); - export default CommandsPage; diff --git a/modern/src/settings/ComputedAttributePage.js b/modern/src/settings/ComputedAttributePage.js index 984339b8..ba5fe712 100644 --- a/modern/src/settings/ComputedAttributePage.js +++ b/modern/src/settings/ComputedAttributePage.js @@ -1,8 +1,17 @@ import React, { useState } from 'react'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControl, InputLabel, MenuItem, Select, TextField, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + FormControl, + InputLabel, + MenuItem, + Select, + TextField, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import { useTranslation } from '../common/components/LocalizationProvider'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; @@ -64,11 +73,11 @@ const ComputedAttributePage = () => { value={item.description || ''} onChange={(event) => setItem({ ...item, description: event.target.value })} label={t('sharedDescription')} - variant="filled" /> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('sharedAttribute')}</InputLabel> <Select + label={t('sharedAttribute')} value={item.attribute || ''} onChange={handleChange} > @@ -84,16 +93,15 @@ const ComputedAttributePage = () => { label={t('sharedExpression')} multiline rows={4} - variant="filled" /> <FormControl - variant="filled" margin="normal" fullWidth disabled={key in positionAttributes} > <InputLabel>{t('sharedType')}</InputLabel> <Select + label={t('sharedType')} value={item.type || ''} onChange={(event) => setItem({ ...item, type: event.target.value })} > diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js index 86704c3b..b754f9fe 100644 --- a/modern/src/settings/ComputedAttributesPage.js +++ b/modern/src/settings/ComputedAttributesPage.js @@ -1,14 +1,15 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import { useAdministrator } from '../common/util/permissions'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -17,10 +18,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { +const ComputedAttributesPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); const administrator = useAdministrator(); @@ -31,46 +33,41 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - {administrator && <TableCell className={classes.columnAction} />} - <TableCell>{t('sharedDescription')}</TableCell> - <TableCell>{t('sharedAttribute')}</TableCell> - <TableCell>{t('sharedExpression')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - {administrator && ( - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - )} - <TableCell>{item.description}</TableCell> - <TableCell>{item.attribute}</TableCell> - <TableCell>{item.expression}</TableCell> - <TableCell>{item.type}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedComputedAttributes']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + {administrator && <TableCell className={classes.columnAction} />} + <TableCell>{t('sharedDescription')}</TableCell> + <TableCell>{t('sharedAttribute')}</TableCell> + <TableCell>{t('sharedExpression')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + {administrator && ( + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/attribute" endpoint="attributes/computed" setTimestamp={setTimestamp} /> + </TableCell> + )} + <TableCell>{item.description}</TableCell> + <TableCell>{item.attribute}</TableCell> + <TableCell>{item.expression}</TableCell> + <TableCell>{item.type}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/attribute" /> + </PageLayout> ); }; -const ComputedAttributesPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedComputedAttributes']}> - <EditCollectionView content={ComputedAttributeView} editPath="/settings/attribute" endpoint="attributes/computed" /> - </PageLayout> -); - export default ComputedAttributesPage; diff --git a/modern/src/settings/DevicePage.js b/modern/src/settings/DevicePage.js index 56a589dc..3f194570 100644 --- a/modern/src/settings/DevicePage.js +++ b/modern/src/settings/DevicePage.js @@ -1,10 +1,16 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + FormControlLabel, + Checkbox, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; import SelectField from '../common/components/SelectField'; @@ -59,14 +65,12 @@ const DevicePage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> <TextField margin="normal" value={item.uniqueId || ''} onChange={(event) => setItem({ ...item, uniqueId: event.target.value })} label={t('deviceIdentifier')} - variant="filled" /> </AccordionDetails> </Accordion> @@ -83,28 +87,24 @@ const DevicePage = () => { onChange={(event) => setItem({ ...item, groupId: Number(event.target.value) })} endpoint="/api/groups" label={t('groupParent')} - variant="filled" /> <TextField margin="normal" value={item.phone || ''} onChange={(event) => setItem({ ...item, phone: event.target.value })} label={t('sharedPhone')} - variant="filled" /> <TextField margin="normal" value={item.model || ''} onChange={(event) => setItem({ ...item, model: event.target.value })} label={t('deviceModel')} - variant="filled" /> <TextField margin="normal" value={item.contact || ''} onChange={(event) => setItem({ ...item, contact: event.target.value })} label={t('deviceContact')} - variant="filled" /> <SelectField margin="normal" @@ -116,7 +116,6 @@ const DevicePage = () => { name: t(`category${category.replace(/^\w/, (c) => c.toUpperCase())}`), }))} label={t('deviceCategory')} - variant="filled" /> {admin && ( <FormControlLabel @@ -156,7 +155,6 @@ const DevicePage = () => { keyBase="deviceId" keyLink="geofenceId" label={t('sharedGeofences')} - variant="filled" /> <LinkField margin="normal" @@ -167,7 +165,6 @@ const DevicePage = () => { keyLink="notificationId" titleGetter={(it) => t(prefixString('event', it.type))} label={t('sharedNotifications')} - variant="filled" /> <LinkField margin="normal" @@ -177,7 +174,6 @@ const DevicePage = () => { keyBase="deviceId" keyLink="driverId" label={t('sharedDrivers')} - variant="filled" /> <LinkField margin="normal" @@ -188,7 +184,6 @@ const DevicePage = () => { keyLink="attributeId" titleGetter={(it) => it.description} label={t('sharedComputedAttributes')} - variant="filled" /> <LinkField margin="normal" @@ -199,7 +194,6 @@ const DevicePage = () => { keyLink="commandId" titleGetter={(it) => it.description} label={t('sharedSavedCommands')} - variant="filled" /> <LinkField margin="normal" @@ -209,7 +203,6 @@ const DevicePage = () => { keyBase="deviceId" keyLink="maintenanceId" label={t('sharedMaintenance')} - variant="filled" /> </AccordionDetails> </Accordion> diff --git a/modern/src/settings/DriverPage.js b/modern/src/settings/DriverPage.js index 93d09c8e..707e2977 100644 --- a/modern/src/settings/DriverPage.js +++ b/modern/src/settings/DriverPage.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, AccordionSummary, AccordionDetails, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; import { useTranslation } from '../common/components/LocalizationProvider'; @@ -46,14 +47,12 @@ const DriverPage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> <TextField margin="normal" value={item.uniqueId || ''} onChange={(event) => setItem({ ...item, uniqueId: event.target.value })} label={t('deviceIdentifier')} - variant="filled" /> </AccordionDetails> </Accordion> diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js index 26601777..c4de30e9 100644 --- a/modern/src/settings/DriversPage.js +++ b/modern/src/settings/DriversPage.js @@ -1,13 +1,14 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +17,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const DriversView = ({ updateTimestamp, onMenuClick }) => { +const DriversPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,40 +31,35 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('deviceIdentifier')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.uniqueId}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedDrivers']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('deviceIdentifier')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/driver" endpoint="drivers" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.uniqueId}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/driver" /> + </PageLayout> ); }; -const DriversPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedDrivers']}> - <EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" /> - </PageLayout> -); - export default DriversPage; diff --git a/modern/src/settings/GeofencePage.js b/modern/src/settings/GeofencePage.js index e23d49fb..353eb2b8 100644 --- a/modern/src/settings/GeofencePage.js +++ b/modern/src/settings/GeofencePage.js @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, AccordionSummary, AccordionDetails, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; import { useTranslation } from '../common/components/LocalizationProvider'; @@ -50,7 +51,6 @@ const GeofencePage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> </AccordionDetails> </Accordion> diff --git a/modern/src/settings/GroupPage.js b/modern/src/settings/GroupPage.js index 9dcd65c9..3f4cc8d8 100644 --- a/modern/src/settings/GroupPage.js +++ b/modern/src/settings/GroupPage.js @@ -1,10 +1,11 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, AccordionSummary, AccordionDetails, Typography, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; import SelectField from '../common/components/SelectField'; @@ -55,7 +56,6 @@ const GroupPage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> </AccordionDetails> </Accordion> @@ -72,7 +72,6 @@ const GroupPage = () => { onChange={(event) => setItem({ ...item, groupId: Number(event.target.value) })} endpoint="/api/groups" label={t('groupParent')} - variant="filled" /> </AccordionDetails> </Accordion> @@ -106,7 +105,6 @@ const GroupPage = () => { keyBase="groupId" keyLink="geofenceId" label={t('sharedGeofences')} - variant="filled" /> <LinkField margin="normal" @@ -117,7 +115,6 @@ const GroupPage = () => { keyLink="notificationId" titleGetter={(it) => t(prefixString('event', it.type))} label={t('sharedNotifications')} - variant="filled" /> <LinkField margin="normal" @@ -127,7 +124,6 @@ const GroupPage = () => { keyBase="groupId" keyLink="driverId" label={t('sharedDrivers')} - variant="filled" /> <LinkField margin="normal" @@ -138,7 +134,6 @@ const GroupPage = () => { keyLink="attributeId" titleGetter={(it) => it.description} label={t('sharedComputedAttributes')} - variant="filled" /> <LinkField margin="normal" @@ -149,7 +144,6 @@ const GroupPage = () => { keyLink="commandId" titleGetter={(it) => it.description} label={t('sharedSavedCommands')} - variant="filled" /> <LinkField margin="normal" @@ -159,7 +153,6 @@ const GroupPage = () => { keyBase="groupId" keyLink="maintenanceId" label={t('sharedMaintenance')} - variant="filled" /> </AccordionDetails> </Accordion> diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js index 257d0bca..ebaa3b5e 100644 --- a/modern/src/settings/GroupsPage.js +++ b/modern/src/settings/GroupsPage.js @@ -1,13 +1,14 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -16,10 +17,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const GroupsView = ({ updateTimestamp, onMenuClick }) => { +const GroupsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -29,38 +31,33 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsGroups']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/group" endpoint="groups" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/group" /> + </PageLayout> ); }; -const GroupsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsGroups']}> - <EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" /> - </PageLayout> -); - export default GroupsPage; diff --git a/modern/src/settings/MaintenancePage.js b/modern/src/settings/MaintenancePage.js index 9e53aca1..5694d77b 100644 --- a/modern/src/settings/MaintenancePage.js +++ b/modern/src/settings/MaintenancePage.js @@ -1,9 +1,18 @@ import React, { useState } from 'react'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, TextField, FormControl, InputLabel, MenuItem, Select, -} from '@material-ui/core'; -import InputAdornment from '@material-ui/core/InputAdornment'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + TextField, + FormControl, + InputLabel, + MenuItem, + Select, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import InputAdornment from '@mui/material/InputAdornment'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { prefixString } from '../common/util/stringUtils'; import EditItemView from './components/EditItemView'; import EditAttributesView from './components/EditAttributesView'; @@ -120,11 +129,11 @@ const MaintenancePage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('sharedType')}</InputLabel> <Select + label={t('sharedType')} value={item.type || ''} onChange={onMaintenanceTypeChange} > @@ -139,7 +148,6 @@ const MaintenancePage = () => { value={rawToValue(item.start) || ''} onChange={(event) => setItem({ ...item, start: valueToRaw(event.target.value) })} label={t('maintenanceStart')} - variant="filled" InputProps={{ endAdornment: <InputAdornment position="start">{labels.start}</InputAdornment>, }} @@ -150,7 +158,6 @@ const MaintenancePage = () => { value={rawToValue(item.period) || ''} onChange={(event) => setItem({ ...item, period: valueToRaw(event.target.value) })} label={t('maintenancePeriod')} - variant="filled" InputProps={{ endAdornment: <InputAdornment position="start">{labels.period}</InputAdornment>, }} diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js index ea00e7e1..d4a06fd2 100644 --- a/modern/src/settings/MaintenancesPage.js +++ b/modern/src/settings/MaintenancesPage.js @@ -1,17 +1,17 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; - import usePositionAttributes from '../common/attributes/usePositionAttributes'; import { formatDistance, formatSpeed } from '../common/util/formatter'; import { useAttributePreference } from '../common/util/preferences'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -20,12 +20,13 @@ const useStyles = makeStyles((theme) => ({ }, })); -const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { +const MaintenacesPage = () => { const classes = useStyles(); const t = useTranslation(); const positionAttributes = usePositionAttributes(t); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); const speedUnit = useAttributePreference('speedUnit'); const distanceUnit = useAttributePreference('distanceUnit'); @@ -37,7 +38,7 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); const convertAttribute = (key, value) => { const attribute = positionAttributes[key]; @@ -56,41 +57,36 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { }; return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - <TableCell>{t('maintenanceStart')}</TableCell> - <TableCell>{t('maintenancePeriod')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.type}</TableCell> - <TableCell>{convertAttribute(item.type, item.start)}</TableCell> - <TableCell>{convertAttribute(item.type, item.period)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedMaintenance']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + <TableCell>{t('maintenanceStart')}</TableCell> + <TableCell>{t('maintenancePeriod')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/maintenance" endpoint="maintenance" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.type}</TableCell> + <TableCell>{convertAttribute(item.type, item.start)}</TableCell> + <TableCell>{convertAttribute(item.type, item.period)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/maintenance" /> + </PageLayout> ); }; -const MaintenacesPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedMaintenance']}> - <EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" /> - </PageLayout> -); - export default MaintenacesPage; diff --git a/modern/src/settings/NotificationPage.js b/modern/src/settings/NotificationPage.js index 38ba19e5..a7c06f68 100644 --- a/modern/src/settings/NotificationPage.js +++ b/modern/src/settings/NotificationPage.js @@ -1,9 +1,15 @@ import React, { useState } from 'react'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + FormControlLabel, + Checkbox, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useTranslation, useTranslationKeys } from '../common/components/LocalizationProvider'; import EditItemView from './components/EditItemView'; import { prefixString, unprefixString } from '../common/util/stringUtils'; @@ -39,60 +45,55 @@ const NotificationPage = () => { breadcrumbs={['settingsTitle', 'sharedNotification']} > {item && ( - <> - <Accordion defaultExpanded> - <AccordionSummary expandIcon={<ExpandMoreIcon />}> - <Typography variant="subtitle1"> - {t('sharedRequired')} - </Typography> - </AccordionSummary> - <AccordionDetails className={classes.details}> - <SelectField - margin="normal" - value={item.type} - emptyValue={null} - onChange={(e) => setItem({ ...item, type: e.target.value })} - endpoint="/api/notifications/types" - keyGetter={(it) => it.type} - titleGetter={(it) => t(prefixString('event', it.type))} - label={t('sharedType')} - variant="filled" - /> - <SelectField - multiple - margin="normal" - value={item.notificators ? item.notificators.split(/[, ]+/) : []} - onChange={(e) => setItem({ ...item, notificators: e.target.value.join() })} - endpoint="/api/notifications/notificators" - keyGetter={(it) => it.type} - titleGetter={(it) => t(prefixString('notificator', it.type))} - label={t('notificationNotificators')} - variant="filled" - /> - {(!item.type || item.type === 'alarm') && ( - <SelectField - multiple - margin="normal" - value={item.attributes && item.attributes.alarms ? item.attributes.alarms.split(/[, ]+/) : []} - onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, alarms: e.target.value.join() } })} - data={alarms} - keyGetter={(it) => it.key} - label={t('sharedAlarms')} - variant="filled" + <Accordion defaultExpanded> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedRequired')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <SelectField + margin="normal" + value={item.type} + emptyValue={null} + onChange={(e) => setItem({ ...item, type: e.target.value })} + endpoint="/api/notifications/types" + keyGetter={(it) => it.type} + titleGetter={(it) => t(prefixString('event', it.type))} + label={t('sharedType')} + /> + <SelectField + multiple + margin="normal" + value={item.notificators ? item.notificators.split(/[, ]+/) : []} + onChange={(e) => setItem({ ...item, notificators: e.target.value.join() })} + endpoint="/api/notifications/notificators" + keyGetter={(it) => it.type} + titleGetter={(it) => t(prefixString('notificator', it.type))} + label={t('notificationNotificators')} + /> + {(!item.type || item.type === 'alarm') && ( + <SelectField + multiple + margin="normal" + value={item.attributes && item.attributes.alarms ? item.attributes.alarms.split(/[, ]+/) : []} + onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, alarms: e.target.value.join() } })} + data={alarms} + keyGetter={(it) => it.key} + label={t('sharedAlarms')} + /> + )} + <FormControlLabel + control={( + <Checkbox + checked={item.always} + onChange={(event) => setItem({ ...item, always: event.target.checked })} /> - )} - <FormControlLabel - control={( - <Checkbox - checked={item.always} - onChange={(event) => setItem({ ...item, always: event.target.checked })} - /> )} - label={t('notificationAlways')} - /> - </AccordionDetails> - </Accordion> - </> + label={t('notificationAlways')} + /> + </AccordionDetails> + </Accordion> )} </EditItemView> ); diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js index de3e762f..ae463dd3 100644 --- a/modern/src/settings/NotificationsPage.js +++ b/modern/src/settings/NotificationsPage.js @@ -1,15 +1,16 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { prefixString } from '../common/util/stringUtils'; import { formatBoolean } from '../common/util/formatter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -18,10 +19,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const NotificationsView = ({ updateTimestamp, onMenuClick }) => { +const NotificationsPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -31,7 +33,7 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); const formatList = (prefix, value) => { if (value) { @@ -45,41 +47,36 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { }; return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('notificationType')}</TableCell> - <TableCell>{t('notificationAlways')}</TableCell> - <TableCell>{t('sharedAlarms')}</TableCell> - <TableCell>{t('notificationNotificators')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{t(prefixString('event', item.type))}</TableCell> - <TableCell>{formatBoolean(item.always, t)}</TableCell> - <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> - <TableCell>{formatList('notificator', item.notificators)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedNotifications']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('notificationType')}</TableCell> + <TableCell>{t('notificationAlways')}</TableCell> + <TableCell>{t('sharedAlarms')}</TableCell> + <TableCell>{t('notificationNotificators')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/notification" endpoint="notifications" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{t(prefixString('event', item.type))}</TableCell> + <TableCell>{formatBoolean(item.always, t)}</TableCell> + <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> + <TableCell>{formatList('notificator', item.notificators)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/notification" /> + </PageLayout> ); }; -const NotificationsPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedNotifications']}> - <EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" /> - </PageLayout> -); - export default NotificationsPage; diff --git a/modern/src/settings/PreferencesPage.js b/modern/src/settings/PreferencesPage.js index 94d55adf..93ded1f0 100644 --- a/modern/src/settings/PreferencesPage.js +++ b/modern/src/settings/PreferencesPage.js @@ -1,8 +1,19 @@ import React from 'react'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Container, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + Container, + FormControl, + InputLabel, + Select, + MenuItem, + Checkbox, + FormControlLabel, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { useLocalization, useTranslation } from '../common/components/LocalizationProvider'; import usePersistedState from '../common/util/usePersistedState'; import PageLayout from '../common/components/PageLayout'; @@ -42,9 +53,13 @@ const PreferencesPage = () => { </Typography> </AccordionSummary> <AccordionDetails className={classes.details}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('loginLanguage')}</InputLabel> - <Select value={language} onChange={(e) => setLanguage(e.target.value)}> + <Select + label={t('loginLanguage')} + value={language} + onChange={(e) => setLanguage(e.target.value)} + > {languageList.map((it) => <MenuItem key={it.code} value={it.code}>{it.name}</MenuItem>)} </Select> </FormControl> @@ -57,9 +72,10 @@ const PreferencesPage = () => { </Typography> </AccordionSummary> <AccordionDetails className={classes.details}> - <FormControl variant="filled" fullWidth> + <FormControl fullWidth> <InputLabel>{t('sharedAttributes')}</InputLabel> <Select + label={t('sharedAttributes')} value={positionItems} onChange={(e) => setPositionItems(e.target.value)} renderValue={(it) => it.length} diff --git a/modern/src/settings/ServerPage.js b/modern/src/settings/ServerPage.js index b745cb96..80353deb 100644 --- a/modern/src/settings/ServerPage.js +++ b/modern/src/settings/ServerPage.js @@ -1,11 +1,23 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Button, FormControl, Container, Checkbox, FormControlLabel, InputLabel, Select, MenuItem, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { useHistory } from 'react-router-dom'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + Button, + FormControl, + Container, + Checkbox, + FormControlLabel, + InputLabel, + Select, + MenuItem, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { useNavigate } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { sessionActions } from '../store'; import EditAttributesView from './components/EditAttributesView'; @@ -35,7 +47,7 @@ const useStyles = makeStyles((theme) => ({ const ServerPage = () => { const classes = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const dispatch = useDispatch(); const t = useTranslation(); @@ -54,7 +66,7 @@ const ServerPage = () => { if (response.ok) { dispatch(sessionActions.updateServer(await response.json())); - history.goBack(); + navigate(-1); } else { throw Error(await response.text()); } @@ -77,7 +89,6 @@ const ServerPage = () => { value={item.mapUrl || ''} onChange={(event) => setItem({ ...item, mapUrl: event.target.value })} label={t('mapCustomLabel')} - variant="filled" /> <TextField margin="normal" @@ -85,7 +96,6 @@ const ServerPage = () => { value={item.latitude || 0} onChange={(event) => setItem({ ...item, latitude: Number(event.target.value) })} label={t('positionLatitude')} - variant="filled" /> <TextField margin="normal" @@ -93,7 +103,6 @@ const ServerPage = () => { value={item.longitude || 0} onChange={(event) => setItem({ ...item, longitude: Number(event.target.value) })} label={t('positionLongitude')} - variant="filled" /> <TextField margin="normal" @@ -101,11 +110,11 @@ const ServerPage = () => { value={item.zoom || 0} onChange={(event) => setItem({ ...item, zoom: Number(event.target.value) })} label={t('serverZoom')} - variant="filled" /> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsCoordinateFormat')}</InputLabel> <Select + label={t('settingsCoordinateFormat')} value={item.coordinateFormat || 'dd'} onChange={(event) => setItem({ ...item, coordinateFormat: event.target.value })} > @@ -114,9 +123,10 @@ const ServerPage = () => { <MenuItem value="dms">{t('sharedDegreesMinutesSeconds')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsSpeedUnit')}</InputLabel> <Select + label={t('settingsSpeedUnit')} value={item.attributes.speedUnit || 'kn'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, speedUnit: e.target.value } })} > @@ -125,9 +135,10 @@ const ServerPage = () => { <MenuItem value="mph">{t('sharedMph')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsDistanceUnit')}</InputLabel> <Select + label={t('settingsDistanceUnit')} value={item.attributes.distanceUnit || 'km'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, distanceUnit: e.target.value } })} > @@ -136,9 +147,10 @@ const ServerPage = () => { <MenuItem value="nmi">{t('sharedNmi')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsVolumeUnit')}</InputLabel> <Select + label={t('settingsVolumeUnit')} value={item.attributes.volumeUnit || 'ltr'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, volumeUnit: e.target.value } })} > @@ -156,21 +168,18 @@ const ServerPage = () => { keyGetter={(it) => it} titleGetter={(it) => it} label={t('sharedTimezone')} - variant="filled" /> <TextField margin="normal" value={item.poiLayer || ''} onChange={(event) => setItem({ ...item, poiLayer: event.target.value })} label={t('mapPoiLayer')} - variant="filled" /> <TextField margin="normal" value={item.announcement || ''} onChange={(event) => setItem({ ...item, announcement: event.target.value })} label={t('serverAnnouncement')} - variant="filled" /> <FormControlLabel control={<Checkbox checked={item.twelveHourFormat} onChange={(event) => setItem({ ...item, twelveHourFormat: event.target.checked })} />} @@ -229,7 +238,7 @@ const ServerPage = () => { )} <FormControl fullWidth margin="normal"> <div className={classes.buttons}> - <Button type="button" color="primary" variant="outlined" onClick={() => history.goBack()}> + <Button type="button" color="primary" variant="outlined" onClick={() => navigate(-1)}> {t('sharedCancel')} </Button> <Button type="button" color="primary" variant="contained" onClick={handleSave}> diff --git a/modern/src/settings/UserPage.js b/modern/src/settings/UserPage.js index a0f6f753..d9238c22 100644 --- a/modern/src/settings/UserPage.js +++ b/modern/src/settings/UserPage.js @@ -1,11 +1,24 @@ import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox, InputAdornment, IconButton, FilledInput, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import CachedIcon from '@material-ui/icons/Cached'; + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + FormControlLabel, + Checkbox, + InputAdornment, + IconButton, + OutlinedInput, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import CachedIcon from '@mui/icons-material/Cached'; import { useDispatch, useSelector } from 'react-redux'; import moment from 'moment'; import EditItemView from './components/EditItemView'; @@ -74,21 +87,18 @@ const UserPage = () => { value={item.name || ''} onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> <TextField margin="normal" value={item.email || ''} onChange={(event) => setItem({ ...item, email: event.target.value })} label={t('userEmail')} - variant="filled" /> <TextField margin="normal" type="password" onChange={(event) => setItem({ ...item, password: event.target.value })} label={t('userPassword')} - variant="filled" /> </AccordionDetails> </Accordion> @@ -104,7 +114,6 @@ const UserPage = () => { value={item.phone || ''} onChange={(event) => setItem({ ...item, phone: event.target.value })} label={t('sharedPhone')} - variant="filled" /> <TextField margin="normal" @@ -112,7 +121,6 @@ const UserPage = () => { value={item.latitude || 0} onChange={(event) => setItem({ ...item, latitude: Number(event.target.value) })} label={t('positionLatitude')} - variant="filled" /> <TextField margin="normal" @@ -120,7 +128,6 @@ const UserPage = () => { value={item.longitude || 0} onChange={(event) => setItem({ ...item, longitude: Number(event.target.value) })} label={t('positionLongitude')} - variant="filled" /> <TextField margin="normal" @@ -128,11 +135,11 @@ const UserPage = () => { value={item.zoom || 0} onChange={(event) => setItem({ ...item, zoom: Number(event.target.value) })} label={t('serverZoom')} - variant="filled" /> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsCoordinateFormat')}</InputLabel> <Select + label={t('settingsCoordinateFormat')} value={item.coordinateFormat || 'dd'} onChange={(event) => setItem({ ...item, coordinateFormat: event.target.value })} > @@ -141,9 +148,10 @@ const UserPage = () => { <MenuItem value="dms">{t('sharedDegreesMinutesSeconds')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsSpeedUnit')}</InputLabel> <Select + label={t('settingsSpeedUnit')} value={(item.attributes && item.attributes.speedUnit) || 'kn'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, speedUnit: e.target.value } })} > @@ -152,9 +160,10 @@ const UserPage = () => { <MenuItem value="mph">{t('sharedMph')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsDistanceUnit')}</InputLabel> <Select + label={t('settingsDistanceUnit')} value={(item.attributes && item.attributes.distanceUnit) || 'km'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, distanceUnit: e.target.value } })} > @@ -163,9 +172,10 @@ const UserPage = () => { <MenuItem value="nmi">{t('sharedNmi')}</MenuItem> </Select> </FormControl> - <FormControl variant="filled" margin="normal" fullWidth> + <FormControl margin="normal" fullWidth> <InputLabel>{t('settingsVolumeUnit')}</InputLabel> <Select + label={t('settingsVolumeUnit')} value={(item.attributes && item.attributes.volumeUnit) || 'ltr'} onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, volumeUnit: e.target.value } })} > @@ -183,14 +193,12 @@ const UserPage = () => { keyGetter={(it) => it} titleGetter={(it) => it} label={t('sharedTimezone')} - variant="filled" /> <TextField margin="normal" value={item.poiLayer || ''} onChange={(event) => setItem({ ...item, poiLayer: event.target.value })} label={t('mapPoiLayer')} - variant="filled" /> <FormControlLabel control={<Checkbox checked={item.twelveHourFormat} onChange={(event) => setItem({ ...item, twelveHourFormat: event.target.checked })} />} @@ -205,18 +213,19 @@ const UserPage = () => { </Typography> </AccordionSummary> <AccordionDetails className={classes.details}> - <FormControl variant="filled" margin="normal"> + <FormControl margin="normal"> <InputLabel>{t('userToken')}</InputLabel> - <FilledInput + <OutlinedInput type="text" value={item.token || ''} onChange={(e) => setItem({ ...item, token: e.target.value })} endAdornment={( <InputAdornment position="end"> - <IconButton onClick={() => { - const token = [...Array(30)].map(() => Math.random().toString(36)[2]).join(''); - setItem({ ...item, token }); - }} + <IconButton + onClick={() => { + const token = [...Array(30)].map(() => Math.random().toString(36)[2]).join(''); + setItem({ ...item, token }); + }} > <CachedIcon /> </IconButton> @@ -226,7 +235,6 @@ const UserPage = () => { </FormControl> <TextField margin="normal" - variant="filled" label={t('userExpirationTime')} type="date" value={(item.expirationTime && item.expirationTime.format(moment.HTML5_FMT.DATE)) || '2999-01-01'} @@ -239,7 +247,6 @@ const UserPage = () => { value={item.deviceLimit || 0} onChange={(e) => setItem({ ...item, deviceLimit: Number(e.target.value) })} label={t('userDeviceLimit')} - variant="filled" disabled={!admin} /> <TextField @@ -248,7 +255,6 @@ const UserPage = () => { value={item.userLimit || 0} onChange={(e) => setItem({ ...item, userLimit: Number(e.target.value) })} label={t('userUserLimit')} - variant="filled" disabled={!admin} /> <FormControlLabel @@ -313,7 +319,6 @@ const UserPage = () => { keyBase="userId" keyLink="deviceId" label={t('deviceTitle')} - variant="filled" /> <LinkField margin="normal" @@ -323,7 +328,6 @@ const UserPage = () => { keyBase="userId" keyLink="groupId" label={t('settingsGroups')} - variant="filled" /> <LinkField margin="normal" @@ -333,7 +337,6 @@ const UserPage = () => { keyBase="userId" keyLink="geofenceId" label={t('sharedGeofences')} - variant="filled" /> <LinkField margin="normal" @@ -344,7 +347,6 @@ const UserPage = () => { keyLink="notificationId" titleGetter={(it) => t(prefixString('event', it.type))} label={t('sharedNotifications')} - variant="filled" /> <LinkField margin="normal" @@ -354,7 +356,6 @@ const UserPage = () => { keyBase="userId" keyLink="calendarId" label={t('sharedCalendars')} - variant="filled" /> <LinkField margin="normal" @@ -364,7 +365,6 @@ const UserPage = () => { keyBase="userId" keyLink="managedUserId" label={t('settingsUsers')} - variant="filled" /> <LinkField margin="normal" @@ -375,7 +375,6 @@ const UserPage = () => { keyLink="attributeId" titleGetter={(it) => it.description} label={t('sharedComputedAttributes')} - variant="filled" /> <LinkField margin="normal" @@ -385,7 +384,6 @@ const UserPage = () => { keyBase="userId" keyLink="driverId" label={t('sharedDrivers')} - variant="filled" /> <LinkField margin="normal" @@ -396,7 +394,6 @@ const UserPage = () => { keyLink="commandId" titleGetter={(it) => it.description} label={t('sharedSavedCommands')} - variant="filled" /> <LinkField margin="normal" @@ -406,7 +403,6 @@ const UserPage = () => { keyBase="userId" keyLink="maintenanceId" label={t('sharedMaintenance')} - variant="filled" /> </AccordionDetails> </Accordion> diff --git a/modern/src/settings/UsersPage.js b/modern/src/settings/UsersPage.js index 235ae4c7..8f3aca46 100644 --- a/modern/src/settings/UsersPage.js +++ b/modern/src/settings/UsersPage.js @@ -1,14 +1,15 @@ import React, { useState } from 'react'; import { - TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, -} from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import { useEffectAsync } from '../reactHelper'; -import EditCollectionView from './components/EditCollectionView'; import { formatBoolean } from '../common/util/formatter'; import { useTranslation } from '../common/components/LocalizationProvider'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; +import CollectionFab from './components/CollectionFab'; +import CollectionActions from './components/CollectionActions'; const useStyles = makeStyles((theme) => ({ columnAction: { @@ -17,10 +18,11 @@ const useStyles = makeStyles((theme) => ({ }, })); -const UsersView = ({ updateTimestamp, onMenuClick }) => { +const UsersPage = () => { const classes = useStyles(); const t = useTranslation(); + const [timestamp, setTimestamp] = useState(Date.now()); const [items, setItems] = useState([]); useEffectAsync(async () => { @@ -30,44 +32,39 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => { } else { throw Error(await response.text()); } - }, [updateTimestamp]); + }, [timestamp]); return ( - <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('userEmail')}</TableCell> - <TableCell>{t('userAdmin')}</TableCell> - <TableCell>{t('sharedDisabled')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map((item) => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> - <IconButton size="small" onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.email}</TableCell> - <TableCell>{formatBoolean(item.administrator, t)}</TableCell> - <TableCell>{formatBoolean(item.disabled, t)}</TableCell> + <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsUsers']}> + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('userEmail')}</TableCell> + <TableCell>{t('userAdmin')}</TableCell> + <TableCell>{t('sharedDisabled')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <CollectionActions itemId={item.id} editPath="/settings/user" endpoint="users" setTimestamp={setTimestamp} /> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.email}</TableCell> + <TableCell>{formatBoolean(item.administrator, t)}</TableCell> + <TableCell>{formatBoolean(item.disabled, t)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + <CollectionFab editPath="/settings/user" /> + </PageLayout> ); }; -const UsersPage = () => ( - <PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsUsers']}> - <EditCollectionView content={UsersView} editPath="/settings/user" endpoint="users" /> - </PageLayout> -); - export default UsersPage; diff --git a/modern/src/settings/components/AddAttributeDialog.js b/modern/src/settings/components/AddAttributeDialog.js index e7965360..de5d22b7 100644 --- a/modern/src/settings/components/AddAttributeDialog.js +++ b/modern/src/settings/components/AddAttributeDialog.js @@ -1,9 +1,10 @@ import React, { useState } from 'react'; import { Button, Dialog, DialogActions, DialogContent, FormControl, InputLabel, MenuItem, Select, TextField, -} from '@material-ui/core'; + Autocomplete, +} from '@mui/material'; -import { Autocomplete, createFilterOptions } from '@material-ui/lab'; +import { createFilterOptions } from '@mui/material/useAutocomplete'; import { useTranslation } from '../../common/components/LocalizationProvider'; const AddAttributeDialog = ({ open, onResult, definitions }) => { @@ -47,17 +48,17 @@ const AddAttributeDialog = ({ open, onResult, definitions }) => { renderOption={(option) => option.name} freeSolo renderInput={(params) => ( - <TextField {...params} label={t('sharedAttribute')} variant="filled" margin="normal" /> + <TextField {...params} label={t('sharedAttribute')} margin="normal" /> )} /> <FormControl - variant="filled" margin="normal" fullWidth disabled={key in definitions} > <InputLabel>{t('sharedType')}</InputLabel> <Select + label={t('sharedType')} value={type} onChange={(e) => setType(e.target.value)} > diff --git a/modern/src/settings/components/BaseCommandView.js b/modern/src/settings/components/BaseCommandView.js index 836e8789..c578e111 100644 --- a/modern/src/settings/components/BaseCommandView.js +++ b/modern/src/settings/components/BaseCommandView.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { TextField, FormControlLabel, Checkbox, -} from '@material-ui/core'; +} from '@mui/material'; import { useTranslation } from '../../common/components/LocalizationProvider'; import SelectField from '../../common/components/SelectField'; import { prefixString } from '../../common/util/stringUtils'; @@ -32,7 +32,6 @@ const BaseCommandView = ({ item, setItem }) => { keyGetter={(it) => it.type} titleGetter={(it) => t(prefixString('command', it.type))} label={t('sharedType')} - variant="filled" /> {attributes.map(({ key, name, type }) => { if (type === 'boolean') { @@ -63,7 +62,6 @@ const BaseCommandView = ({ item, setItem }) => { setItem(updateItem); }} label={name} - variant="filled" /> ); })} diff --git a/modern/src/settings/components/CollectionActions.js b/modern/src/settings/components/CollectionActions.js new file mode 100644 index 00000000..c5e14949 --- /dev/null +++ b/modern/src/settings/components/CollectionActions.js @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { IconButton, Menu, MenuItem } from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { useNavigate } from 'react-router-dom'; +import RemoveDialog from '../../common/components/RemoveDialog'; +import { useTranslation } from '../../common/components/LocalizationProvider'; + +const CollectionActions = ({ + itemId, editPath, endpoint, setTimestamp, +}) => { + const navigate = useNavigate(); + const t = useTranslation(); + + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [removing, setRemoving] = useState(false); + + const handleEdit = () => { + navigate(`${editPath}/${itemId}`); + setMenuAnchorEl(null); + }; + + const handleRemove = () => { + setRemoving(true); + setMenuAnchorEl(null); + }; + + const handleRemoveResult = (removed) => { + setRemoving(false); + if (removed) { + setTimestamp(Date.now()); + } + }; + + return ( + <> + <IconButton size="small" onClick={(event) => setMenuAnchorEl(event.currentTarget)}> + <MoreVertIcon /> + </IconButton> + <Menu open={!!menuAnchorEl} anchorEl={menuAnchorEl} onClose={() => setMenuAnchorEl(null)}> + <MenuItem onClick={handleEdit}>{t('sharedEdit')}</MenuItem> + <MenuItem onClick={handleRemove}>{t('sharedRemove')}</MenuItem> + </Menu> + <RemoveDialog style={{ transform: 'none' }} open={removing} endpoint={endpoint} itemId={itemId} onResult={handleRemoveResult} /> + </> + ); +}; + +export default CollectionActions; diff --git a/modern/src/settings/components/CollectionFab.js b/modern/src/settings/components/CollectionFab.js new file mode 100644 index 00000000..2fb5b5c9 --- /dev/null +++ b/modern/src/settings/components/CollectionFab.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Fab } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import AddIcon from '@mui/icons-material/Add'; +import { useNavigate } from 'react-router-dom'; +import { useReadonly } from '../../common/util/permissions'; +import dimensions from '../../common/theme/dimensions'; + +const useStyles = makeStyles((theme) => ({ + fab: { + position: 'fixed', + bottom: theme.spacing(2), + right: theme.spacing(2), + [theme.breakpoints.down('md')]: { + bottom: dimensions.bottomBarHeight + theme.spacing(2), + }, + }, +})); + +const CollectionFab = ({ editPath, disabled }) => { + const classes = useStyles(); + const navigate = useNavigate(); + + const readonly = useReadonly(); + + if (!readonly && !disabled) { + return ( + <Fab size="medium" color="primary" className={classes.fab} onClick={() => navigate(editPath)}> + <AddIcon /> + </Fab> + ); + } + return ''; +}; + +export default CollectionFab; diff --git a/modern/src/settings/components/EditAttributesView.js b/modern/src/settings/components/EditAttributesView.js index e28909e9..e6e1ddf0 100644 --- a/modern/src/settings/components/EditAttributesView.js +++ b/modern/src/settings/components/EditAttributesView.js @@ -1,10 +1,19 @@ import React, { useState } from 'react'; import { - Button, Checkbox, FilledInput, FormControl, FormControlLabel, Grid, IconButton, InputAdornment, InputLabel, makeStyles, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import AddIcon from '@material-ui/icons/Add'; + Button, + Checkbox, + OutlinedInput, + FormControl, + FormControlLabel, + Grid, + IconButton, + InputAdornment, + InputLabel, +} from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import CloseIcon from '@mui/icons-material/Close'; +import AddIcon from '@mui/icons-material/Add'; import AddAttributeDialog from './AddAttributeDialog'; import { useTranslation } from '../../common/components/LocalizationProvider'; import { useAttributePreference } from '../../common/util/preferences'; @@ -164,9 +173,9 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { ); } return ( - <FormControl variant="filled" margin="normal" key={key}> + <FormControl margin="normal" key={key}> <InputLabel>{getAttributeName(key, subtype)}</InputLabel> - <FilledInput + <OutlinedInput type={type === 'number' ? 'number' : 'text'} value={getDisplayValue(value, subtype)} onChange={(e) => updateAttribute(key, e.target.value, type, subtype)} @@ -182,7 +191,6 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { ); })} <Button - size="large" variant="outlined" color="primary" onClick={() => setAddDialogShown(true)} diff --git a/modern/src/settings/components/EditCollectionView.js b/modern/src/settings/components/EditCollectionView.js deleted file mode 100644 index b0bf3ea0..00000000 --- a/modern/src/settings/components/EditCollectionView.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useState } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { useHistory } from 'react-router-dom'; -import Menu from '@material-ui/core/Menu'; -import MenuItem from '@material-ui/core/MenuItem'; -import Fab from '@material-ui/core/Fab'; -import AddIcon from '@material-ui/icons/Add'; - -import RemoveDialog from '../../common/components/RemoveDialog'; -import { useTranslation } from '../../common/components/LocalizationProvider'; -import dimensions from '../../common/theme/dimensions'; -import { useReadonly } from '../../common/util/permissions'; - -const useStyles = makeStyles((theme) => ({ - fab: { - position: 'fixed', - bottom: theme.spacing(2), - right: theme.spacing(2), - [theme.breakpoints.down('sm')]: { - bottom: dimensions.bottomBarHeight + theme.spacing(2), - }, - }, -})); - -const EditCollectionView = ({ - content, editPath, endpoint, disableAdd, filter, -}) => { - const classes = useStyles(); - const history = useHistory(); - const t = useTranslation(); - - const readonly = useReadonly(); - - const [selectedId, setSelectedId] = useState(null); - const [selectedAnchorEl, setSelectedAnchorEl] = useState(null); - const [removeDialogShown, setRemoveDialogShown] = useState(false); - const [updateTimestamp, setUpdateTimestamp] = useState(Date.now()); - - const menuShow = (anchorId, itemId) => { - setSelectedAnchorEl(anchorId); - setSelectedId(itemId); - }; - - const menuHide = () => { - setSelectedAnchorEl(null); - }; - - const handleAdd = () => { - history.push(editPath); - menuHide(); - }; - - const handleEdit = () => { - history.push(`${editPath}/${selectedId}`); - menuHide(); - }; - - const handleRemove = () => { - setRemoveDialogShown(true); - menuHide(); - }; - - const handleRemoveResult = (removed) => { - setRemoveDialogShown(false); - if (removed) { - setUpdateTimestamp(Date.now()); - } - }; - - const Content = content; - - return ( - <> - <Content updateTimestamp={updateTimestamp} onMenuClick={menuShow} filter={filter} /> - {!readonly && !disableAdd && ( - <Fab size="medium" color="primary" className={classes.fab} onClick={handleAdd}> - <AddIcon /> - </Fab> - )} - <Menu open={!!selectedAnchorEl} anchorEl={selectedAnchorEl} onClose={menuHide}> - <MenuItem onClick={handleEdit}>{t('sharedEdit')}</MenuItem> - <MenuItem onClick={handleRemove}>{t('sharedRemove')}</MenuItem> - </Menu> - <RemoveDialog open={removeDialogShown} endpoint={endpoint} itemId={selectedId} onResult={handleRemoveResult} /> - </> - ); -}; - -export default EditCollectionView; diff --git a/modern/src/settings/components/EditItemView.js b/modern/src/settings/components/EditItemView.js index 28598e77..83624b8f 100644 --- a/modern/src/settings/components/EditItemView.js +++ b/modern/src/settings/components/EditItemView.js @@ -1,9 +1,9 @@ import React from 'react'; -import { useHistory, useParams } from 'react-router-dom'; -import { makeStyles } from '@material-ui/core/styles'; -import Container from '@material-ui/core/Container'; -import Button from '@material-ui/core/Button'; -import FormControl from '@material-ui/core/FormControl'; +import { useNavigate, useParams } from 'react-router-dom'; +import makeStyles from '@mui/styles/makeStyles'; +import Container from '@mui/material/Container'; +import Button from '@mui/material/Button'; +import FormControl from '@mui/material/FormControl'; import { useCatch, useEffectAsync } from '../../reactHelper'; import { useTranslation } from '../../common/components/LocalizationProvider'; @@ -25,7 +25,7 @@ const useStyles = makeStyles((theme) => ({ const EditItemView = ({ children, endpoint, item, setItem, defaultItem, validate, onItemSaved, menu, breadcrumbs, }) => { - const history = useHistory(); + const navigate = useNavigate(); const classes = useStyles(); const t = useTranslation(); @@ -60,7 +60,7 @@ const EditItemView = ({ if (onItemSaved) { onItemSaved(await response.json()); } - history.goBack(); + navigate(-1); } else { throw Error(await response.text()); } @@ -76,7 +76,7 @@ const EditItemView = ({ type="button" color="primary" variant="outlined" - onClick={() => history.goBack()} + onClick={() => navigate(-1)} > {t('sharedCancel')} </Button> diff --git a/modern/src/settings/components/SettingsMenu.js b/modern/src/settings/components/SettingsMenu.js index 036f4101..cb2dba2f 100644 --- a/modern/src/settings/components/SettingsMenu.js +++ b/modern/src/settings/components/SettingsMenu.js @@ -1,17 +1,17 @@ import React from 'react'; import { Divider, List, ListItem, ListItemIcon, ListItemText, -} from '@material-ui/core'; -import SettingsIcon from '@material-ui/icons/Settings'; -import CreateIcon from '@material-ui/icons/Create'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import FolderIcon from '@material-ui/icons/Folder'; -import PersonIcon from '@material-ui/icons/Person'; -import StorageIcon from '@material-ui/icons/Storage'; -import BuildIcon from '@material-ui/icons/Build'; -import PeopleIcon from '@material-ui/icons/People'; -import TodayIcon from '@material-ui/icons/Today'; -import PublishIcon from '@material-ui/icons/Publish'; +} from '@mui/material'; +import SettingsIcon from '@mui/icons-material/Settings'; +import CreateIcon from '@mui/icons-material/Create'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import FolderIcon from '@mui/icons-material/Folder'; +import PersonIcon from '@mui/icons-material/Person'; +import StorageIcon from '@mui/icons-material/Storage'; +import BuildIcon from '@mui/icons-material/Build'; +import PeopleIcon from '@mui/icons-material/People'; +import TodayIcon from '@mui/icons-material/Today'; +import PublishIcon from '@mui/icons-material/Publish'; import { Link, useLocation } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { useTranslation } from '../../common/components/LocalizationProvider'; |