diff options
Diffstat (limited to 'modern')
89 files changed, 1661 insertions, 1484 deletions
diff --git a/modern/.eslintrc.js b/modern/.eslintrc.js new file mode 100644 index 00000000..a1089804 --- /dev/null +++ b/modern/.eslintrc.js @@ -0,0 +1,18 @@ +module.exports = { + "extends": "airbnb", + "plugins": [ + "react" + ], + "ignorePatterns": ["serviceWorker.js", "localization.js", "switcher.js"], + "rules": { + "max-len": [0], + "no-shadow": [0], + "no-return-assign": [0], + "no-param-reassign": [0], + "no-prototype-builtins": [0], + "react/jsx-filename-extension": [1, { "extensions": [".js"] }], + "react/prop-types": [0], + "react/jsx-props-no-spreading": [0], + "jsx-a11y/anchor-is-valid": [0] + } +}; diff --git a/modern/craco.config.js b/modern/craco.config.js index 3e5d98e9..32971f24 100644 --- a/modern/craco.config.js +++ b/modern/craco.config.js @@ -1,12 +1,12 @@ module.exports = { webpack: { - configure: webpackConfig => { + configure: (webpackConfig) => { const scopePluginIndex = webpackConfig.resolve.plugins.findIndex( - ({ constructor }) => constructor && constructor.name === 'ModuleScopePlugin' + ({ constructor }) => constructor && constructor.name === 'ModuleScopePlugin', ); webpackConfig.resolve.plugins.splice(scopePluginIndex, 1); return webpackConfig; - } - } + }, + }, }; diff --git a/modern/package.json b/modern/package.json index 6c74cbe5..f986b314 100644 --- a/modern/package.json +++ b/modern/package.json @@ -10,6 +10,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", "@reduxjs/toolkit": "^1.6.0", + "@turf/circle": "^6.5.0", "@turf/turf": "^6.4.0", "canvas-tint-image": "^2.0.1", "maplibre-gl": "^1.15.0", @@ -31,7 +32,9 @@ "start": "craco start", "build": "craco build", "build_release": "PUBLIC_URL=/modern/ craco build", - "test": "craco test" + "test": "craco test", + "lint": "eslint .", + "lint:fix": "eslint --fix --ext .js ." }, "browserslist": { "production": [ @@ -44,5 +47,11 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "eslint": "^6.8.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^1.7.0" } } diff --git a/modern/src/App.js b/modern/src/App.js index b76995ea..ac7cdb26 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -1,7 +1,9 @@ import React from 'react'; import { ThemeProvider } from '@material-ui/core/styles'; -import { Switch, Route } from 'react-router-dom' +import { Switch, Route } from 'react-router-dom'; import CssBaseline from '@material-ui/core/CssBaseline'; +import { useSelector } from 'react-redux'; +import { LinearProgress } from '@material-ui/core'; import MainPage from './MainPage'; import RouteReportPage from './reports/RouteReportPage'; import ServerPage from './admin/ServerPage'; @@ -16,8 +18,6 @@ import GroupPage from './settings/GroupPage'; import PositionPage from './PositionPage'; import EventReportPage from './reports/EventReportPage'; import ReplayPage from './reports/ReplayPage'; -import { useSelector } from 'react-redux'; -import { LinearProgress } from '@material-ui/core'; import TripReportPage from './reports/TripReportPage'; import StopReportPage from './reports/StopReportPage'; import SummaryReportPage from './reports/SummaryReportPage'; @@ -40,7 +40,7 @@ import GeofencesPage from './GeofencesPage'; import GeofencePage from './GeofencePage'; const App = () => { - const initialized = useSelector(state => !!state.session.server && !!state.session.user); + const initialized = useSelector((state) => !!state.session.server && !!state.session.user); return ( <ThemeProvider theme={theme}> @@ -48,44 +48,44 @@ const App = () => { <SocketController /> <CachingController /> <Switch> - <Route exact path='/login' component={LoginForm} /> - <Route exact path='/register' component={RegisterForm} /> - <Route exact path='/reset-password' component={ResetPasswordForm} /> + <Route exact path="/login" component={LoginForm} /> + <Route exact path="/register" component={RegisterForm} /> + <Route exact path="/reset-password" component={ResetPasswordForm} /> <Route> {!initialized ? (<LinearProgress />) : ( <Switch> - <Route exact path='/' component={MainPage} /> - <Route exact path='/replay' component={ReplayPage} /> - <Route exact path='/position/:id?' component={PositionPage} /> - <Route exact path='/user/:id?' component={UserPage} /> - <Route exact path='/device/:id?' component={DevicePage} /> - <Route exact path='/geofence/:id?' component={GeofencePage} /> - <Route exact path='/geofences' component={GeofencesPage} /> - <Route exact path='/settings/notifications' component={NotificationsPage} /> - <Route exact path='/settings/notification/:id?' component={NotificationPage} /> - <Route exact path='/settings/groups' component={GroupsPage} /> - <Route exact path='/settings/group/:id?' component={GroupPage} /> - <Route exact path='/settings/drivers' component={DriversPage} /> - <Route exact path='/settings/driver/:id?' component={DriverPage} /> - <Route exact path='/settings/attributes' component={ComputedAttributesPage} /> - <Route exact path='/settings/attribute/:id?' component={ComputedAttributePage} /> - <Route exact path='/settings/maintenances' component={MaintenancesPage} /> - <Route exact path='/settings/maintenance/:id?' component={MaintenancePage} /> - <Route exact path='/admin/server' component={ServerPage} /> - <Route exact path='/admin/users' component={UsersPage} /> - <Route exact path='/admin/statistics' component={StatisticsPage} /> - <Route exact path='/reports/route' component={RouteReportPage} /> - <Route exact path='/reports/event' component={EventReportPage} /> - <Route exact path='/reports/trip' component={TripReportPage} /> - <Route exact path='/reports/stop' component={StopReportPage} /> - <Route exact path='/reports/summary' component={SummaryReportPage} /> - <Route exact path='/reports/chart' component={ChartReportPage} /> + <Route exact path="/" component={MainPage} /> + <Route exact path="/replay" component={ReplayPage} /> + <Route exact path="/position/:id?" component={PositionPage} /> + <Route exact path="/user/:id?" component={UserPage} /> + <Route exact path="/device/:id?" component={DevicePage} /> + <Route exact path="/geofence/:id?" component={GeofencePage} /> + <Route exact path="/geofences" component={GeofencesPage} /> + <Route exact path="/settings/notifications" component={NotificationsPage} /> + <Route exact path="/settings/notification/:id?" component={NotificationPage} /> + <Route exact path="/settings/groups" component={GroupsPage} /> + <Route exact path="/settings/group/:id?" component={GroupPage} /> + <Route exact path="/settings/drivers" component={DriversPage} /> + <Route exact path="/settings/driver/:id?" component={DriverPage} /> + <Route exact path="/settings/attributes" component={ComputedAttributesPage} /> + <Route exact path="/settings/attribute/:id?" component={ComputedAttributePage} /> + <Route exact path="/settings/maintenances" component={MaintenancesPage} /> + <Route exact path="/settings/maintenance/:id?" component={MaintenancePage} /> + <Route exact path="/admin/server" component={ServerPage} /> + <Route exact path="/admin/users" component={UsersPage} /> + <Route exact path="/admin/statistics" component={StatisticsPage} /> + <Route exact path="/reports/route" component={RouteReportPage} /> + <Route exact path="/reports/event" component={EventReportPage} /> + <Route exact path="/reports/trip" component={TripReportPage} /> + <Route exact path="/reports/stop" component={StopReportPage} /> + <Route exact path="/reports/summary" component={SummaryReportPage} /> + <Route exact path="/reports/chart" component={ChartReportPage} /> </Switch> )} </Route> </Switch> </ThemeProvider> ); -} +}; export default App; diff --git a/modern/src/CachingController.js b/modern/src/CachingController.js index 3f808de2..d6e20060 100644 --- a/modern/src/CachingController.js +++ b/modern/src/CachingController.js @@ -1,10 +1,12 @@ -import { useDispatch, useSelector } from 'react-redux'; -import { connect } from 'react-redux'; -import { geofencesActions, groupsActions, driversActions, maintenancesActions } from './store'; +import { useDispatch, useSelector, connect } from 'react-redux'; + +import { + geofencesActions, groupsActions, driversActions, maintenancesActions, +} from './store'; import { useEffectAsync } from './reactHelper'; const CachingController = () => { - const authenticated = useSelector(state => !!state.session.user); + const authenticated = useSelector((state) => !!state.session.user); const dispatch = useDispatch(); useEffectAsync(async () => { @@ -23,8 +25,8 @@ const CachingController = () => { dispatch(groupsActions.update(await response.json())); } } - }, [authenticated]); - + }, [authenticated]); + useEffectAsync(async () => { if (authenticated) { const response = await fetch('/api/drivers'); @@ -33,7 +35,7 @@ const CachingController = () => { } } }, [authenticated]); - + useEffectAsync(async () => { if (authenticated) { const response = await fetch('/api/maintenance'); @@ -41,9 +43,9 @@ const CachingController = () => { dispatch(maintenancesActions.update(await response.json())); } } - }, [authenticated]); - + }, [authenticated]); + return null; -} +}; export default connect()(CachingController); diff --git a/modern/src/DevicePage.js b/modern/src/DevicePage.js index ed5a6cba..699e97c6 100644 --- a/modern/src/DevicePage.js +++ b/modern/src/DevicePage.js @@ -1,14 +1,16 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from './common/localization'; import EditItemView from './EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditAttributesView from './attributes/EditAttributesView'; import deviceAttributes from './attributes/deviceAttributes'; import SelectField from './form/SelectField'; -import { deviceCategories } from './common/deviceCategories'; +import deviceCategories from './common/deviceCategories'; import LinkField from './form/LinkField'; import { prefixString } from './common/stringUtils'; @@ -25,7 +27,8 @@ const DevicePage = () => { return ( <EditItemView endpoint="devices" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -37,15 +40,17 @@ const DevicePage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.uniqueId || ''} - onChange={event => setItem({...item, uniqueId: event.target.value})} + onChange={(event) => setItem({ ...item, uniqueId: event.target.value })} label={t('deviceIdentifier')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -58,42 +63,48 @@ const DevicePage = () => { <SelectField margin="normal" value={item.groupId || 0} - onChange={event => setItem({...item, groupId: Number(event.target.value)})} + onChange={(event) => setItem({ ...item, groupId: Number(event.target.value) })} endpoint="/api/groups" label={t('groupParent')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.phone || ''} - onChange={event => setItem({...item, phone: event.target.value})} + onChange={(event) => setItem({ ...item, phone: event.target.value })} label={t('sharedPhone')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.model || ''} - onChange={event => setItem({...item, model: event.target.value})} + onChange={(event) => setItem({ ...item, model: event.target.value })} label={t('deviceModel')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.contact || ''} - onChange={event => setItem({...item, contact: event.target.value})} + onChange={(event) => setItem({ ...item, contact: event.target.value })} label={t('deviceContact')} - variant="filled" /> + variant="filled" + /> <SelectField margin="normal" value={item.category || 'default'} emptyValue={null} - onChange={event => setItem({...item, category: event.target.value})} - data={deviceCategories.map(category => ({ + onChange={(event) => setItem({ ...item, category: event.target.value })} + data={deviceCategories.map((category) => ({ id: category, - name: t(`category${category.replace(/^\w/, c => c.toUpperCase())}`) + name: t(`category${category.replace(/^\w/, (c) => c.toUpperCase())}`), }))} label={t('deviceCategory')} - variant="filled" /> + variant="filled" + /> <FormControlLabel - control={<Checkbox checked={item.disabled} onChange={event => setItem({...item, disabled: event.target.checked})} />} - label={t('sharedDisabled')} /> + control={<Checkbox checked={item.disabled} onChange={(event) => setItem({ ...item, disabled: event.target.checked })} />} + label={t('sharedDisabled')} + /> </AccordionDetails> </Accordion> <Accordion> @@ -105,12 +116,13 @@ const DevicePage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={deviceAttributes} - /> + /> </AccordionDetails> </Accordion> - {item.id && + {item.id + && ( <Accordion> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="subtitle1"> @@ -121,57 +133,62 @@ const DevicePage = () => { <LinkField margin="normal" endpointAll="/api/geofences" - endpointLinked={"/api/geofences?deviceId=" + item.id} + endpointLinked={`/api/geofences?deviceId=${item.id}`} baseId={item.id} keyBase="deviceId" keyLink="geofenceId" label={t('sharedGeofences')} - variant="filled" /> + variant="filled" + /> <LinkField margin="normal" endpointAll="/api/notifications" - endpointLinked={"/api/notifications?deviceId=" + item.id} + endpointLinked={`/api/notifications?deviceId=${item.id}`} baseId={item.id} keyBase="deviceId" keyLink="notificationId" - titleGetter={it => t(prefixString('event', it.type))} + titleGetter={(it) => t(prefixString('event', it.type))} label={t('sharedNotifications')} - variant="filled" /> + variant="filled" + /> <LinkField margin="normal" endpointAll="/api/drivers" - endpointLinked={"/api/drivers?deviceId=" + item.id} + endpointLinked={`/api/drivers?deviceId=${item.id}`} baseId={item.id} keyBase="deviceId" keyLink="driverId" label={t('sharedDrivers')} - variant="filled" /> + variant="filled" + /> <LinkField margin="normal" endpointAll="/api/attributes/computed" - endpointLinked={"/api/attributes/computed?deviceId=" + item.id} + endpointLinked={`/api/attributes/computed?deviceId=${item.id}`} baseId={item.id} keyBase="deviceId" keyLink="attributeId" - titleGetter={it => it.description} + titleGetter={(it) => it.description} label={t('sharedComputedAttributes')} - variant="filled" /> + variant="filled" + /> <LinkField margin="normal" endpointAll="/api/maintenance" - endpointLinked={"/api/maintenance?deviceId=" + item.id} + endpointLinked={`/api/maintenance?deviceId=${item.id}`} baseId={item.id} keyBase="deviceId" keyLink="maintenanceId" label={t('sharedMaintenance')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> - } + )} </> - } + )} </EditItemView> ); -} +}; export default DevicePage; diff --git a/modern/src/DevicesList.js b/modern/src/DevicesList.js index f66d717d..85b936ce 100644 --- a/modern/src/DevicesList.js +++ b/modern/src/DevicesList.js @@ -34,7 +34,7 @@ const DeviceRow = ({ data, index, style }) => { const { items, onMenuClick } = data; const item = items[index]; - + return ( <div style={style}> <Fragment key={index}> @@ -61,7 +61,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick }) => { const classes = useStyles(); const dispatch = useDispatch(); - const items = useSelector(state => Object.values(state.devices.items)); + const items = useSelector((state) => Object.values(state.devices.items)); useEffectAsync(async () => { const response = await fetch('/api/devices'); @@ -79,7 +79,8 @@ const DeviceView = ({ updateTimestamp, onMenuClick }) => { height={height} itemCount={items.length} itemData={{ items, onMenuClick }} - itemSize={72 + 1} > + itemSize={72 + 1} + > {DeviceRow} </FixedSizeList> </List> @@ -88,10 +89,8 @@ const DeviceView = ({ updateTimestamp, onMenuClick }) => { ); }; -const DevicesList = () => { - return ( - <EditCollectionView content={DeviceView} editPath="/device" endpoint="devices" /> - ); -}; +const DevicesList = () => ( + <EditCollectionView content={DeviceView} editPath="/device" endpoint="devices" /> +); export default DevicesList; diff --git a/modern/src/EditCollectionView.js b/modern/src/EditCollectionView.js index 572a1d19..7d5e4b27 100644 --- a/modern/src/EditCollectionView.js +++ b/modern/src/EditCollectionView.js @@ -10,7 +10,7 @@ import { useSelector } from 'react-redux'; import t from './common/localization'; import RemoveDialog from './RemoveDialog'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ fab: { position: 'absolute', bottom: theme.spacing(2), @@ -18,7 +18,9 @@ const useStyles = makeStyles(theme => ({ }, })); -const EditCollectionView = ({ content, editPath, endpoint, disableAdd }) => { +const EditCollectionView = ({ + content, editPath, endpoint, disableAdd, +}) => { const classes = useStyles(); const history = useHistory(); @@ -26,47 +28,48 @@ const EditCollectionView = ({ content, editPath, endpoint, disableAdd }) => { const [selectedAnchorEl, setSelectedAnchorEl] = useState(null); const [removeDialogShown, setRemoveDialogShown] = useState(false); const [updateTimestamp, setUpdateTimestamp] = useState(Date.now()); - const adminEnabled = useSelector(state => state.session.user && state.session.user.administrator); + const adminEnabled = useSelector((state) => state.session.user && state.session.user.administrator); 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 = () => { setRemoveDialogShown(false); setUpdateTimestamp(Date.now()); - } + }; const Content = content; return ( <> <Content updateTimestamp={updateTimestamp} onMenuClick={menuShow} /> - {adminEnabled && !disableAdd && + {adminEnabled && !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> @@ -74,6 +77,6 @@ const EditCollectionView = ({ content, editPath, endpoint, disableAdd }) => { <RemoveDialog open={removeDialogShown} endpoint={endpoint} itemId={selectedId} onResult={handleRemoveResult} /> </> ); -} +}; export default EditCollectionView; diff --git a/modern/src/EditItemView.js b/modern/src/EditItemView.js index 16fbbaee..9bf91a6a 100644 --- a/modern/src/EditItemView.js +++ b/modern/src/EditItemView.js @@ -1,15 +1,15 @@ import React from 'react'; -import MainToolbar from './MainToolbar'; 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 MainToolbar from './MainToolbar'; import t from './common/localization'; import { useEffectAsync } from './reactHelper'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { marginTop: theme.spacing(2), }, @@ -22,7 +22,9 @@ const useStyles = makeStyles(theme => ({ }, })); -const EditItemView = ({ children, endpoint, item, setItem }) => { +const EditItemView = ({ + children, endpoint, item, setItem, +}) => { const history = useHistory(); const classes = useStyles(); const { id } = useParams(); @@ -58,14 +60,14 @@ const EditItemView = ({ children, endpoint, item, setItem }) => { return ( <> <MainToolbar /> - <Container maxWidth='xs' className={classes.container}> + <Container maxWidth="xs" className={classes.container}> {children} - <FormControl fullWidth margin='normal'> + <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={() => history.goBack()}> {t('sharedCancel')} </Button> - <Button type='button' color='primary' variant='contained' onClick={handleSave}> + <Button type="button" color="primary" variant="contained" onClick={handleSave}> {t('sharedSave')} </Button> </div> @@ -73,6 +75,6 @@ const EditItemView = ({ children, endpoint, item, setItem }) => { </Container> </> ); -} +}; export default EditItemView; diff --git a/modern/src/GeofencePage.js b/modern/src/GeofencePage.js index 6c5db9bb..06456fca 100644 --- a/modern/src/GeofencePage.js +++ b/modern/src/GeofencePage.js @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from './common/localization'; import EditItemView from './EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditAttributesView from './attributes/EditAttributesView'; import geofenceAttributes from './attributes/geofenceAttributes'; @@ -21,7 +23,8 @@ const GeofencePage = () => { return ( <EditItemView endpoint="geofences" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -33,9 +36,10 @@ const GeofencePage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -47,15 +51,15 @@ const GeofencePage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={geofenceAttributes} - /> + /> </AccordionDetails> </Accordion> </> - } + )} </EditItemView> ); -} +}; export default GeofencePage; diff --git a/modern/src/GeofencesList.js b/modern/src/GeofencesList.js index 2988bef1..572ac5b1 100644 --- a/modern/src/GeofencesList.js +++ b/modern/src/GeofencesList.js @@ -28,7 +28,7 @@ const GeofenceView = ({ onMenuClick }) => { const classes = useStyles(); const dispatch = useDispatch(); - const items = useSelector(state => Object.values(state.geofences.items)); + const items = useSelector((state) => Object.values(state.geofences.items)); return ( <List className={classes.list}> @@ -47,12 +47,10 @@ const GeofenceView = ({ onMenuClick }) => { ))} </List> ); -} +}; -const GeofencesList = () => { - return ( - <EditCollectionView content={GeofenceView} editPath="/geofence" endpoint="geofences" disableAdd /> - ); -} +const GeofencesList = () => ( + <EditCollectionView content={GeofenceView} editPath="/geofence" endpoint="geofences" disableAdd /> +); export default GeofencesList; diff --git a/modern/src/GeofencesPage.js b/modern/src/GeofencesPage.js index 389ac998..95c7151e 100644 --- a/modern/src/GeofencesPage.js +++ b/modern/src/GeofencesPage.js @@ -8,7 +8,7 @@ import CurrentLocationMap from './map/CurrentLocationMap'; import GeofenceEditMap from './map/GeofenceEditMap'; import GeofencesList from './GeofencesList'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { height: '100%', display: 'flex', @@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({ flexDirection: 'row', [theme.breakpoints.down('xs')]: { flexDirection: 'column-reverse', - } + }, }, drawerPaper: { position: 'relative', @@ -30,7 +30,7 @@ const useStyles = makeStyles(theme => ({ }, [theme.breakpoints.down('xs')]: { height: 250, - } + }, }, mapContainer: { flexGrow: 1, @@ -46,8 +46,9 @@ const GeofencesPage = ({ width }) => { <div className={classes.content}> <Drawer anchor={isWidthUp('sm', width) ? 'left' : 'bottom'} - variant='permanent' - classes={{ paper: classes.drawerPaper }}> + variant="permanent" + classes={{ paper: classes.drawerPaper }} + > <GeofencesList /> </Drawer> <div className={classes.mapContainer}> @@ -61,6 +62,6 @@ const GeofencesPage = ({ width }) => { </div> </div> ); -} +}; export default withWidth()(GeofencesPage); diff --git a/modern/src/Logo.js b/modern/src/Logo.js index bea14d8c..1fb1ac8f 100644 --- a/modern/src/Logo.js +++ b/modern/src/Logo.js @@ -1,31 +1,28 @@ import React from 'react'; -const Logo = ({ fill }) => { - - return ( - <svg xmlns='http://www.w3.org/2000/svg' height="64" viewBox="0 0 240 64" width="240" version="1.1"> - <g id="layer1"> - <rect id="rect3778" height="64" width="236.1" y="0" x="0" fill="none"/> - <ellipse id="path3038" rx="28.995" ry="28.995" transform="rotate(-30)" cy="43.713" cx="11.713" stroke-width="10.699" fill="#fff"/> - <g fill= {fill}> - <circle id="path2993" strokeWidth="1.3262" transform="rotate(-30)" cy="43.713" cx="9.4364" r="2.2765"/> - <path id="path3004" d="m37.012 24.177-2.8428 3.6128c0.66345 0.52205 1.3255 1.1576 1.7734 1.9333 0.4479 0.77578 0.66726 1.6669 0.78764 2.5025l4.5502-0.65558c-0.193-1.42-0.633-2.804-1.394-4.123s-1.74-2.391-2.874-3.27z" strokeWidth="1.0095"/> - <path id="path3014" d="m42.504 16.9-2.8428 3.6128c1.607 1.2355 3.0914 2.7935 4.1679 4.6581s1.6835 3.9291 1.95 5.9386l4.5502-0.65558c-0.33967-2.5954-1.1669-5.1513-2.5573-7.5594-1.3903-2.4081-3.1901-4.4025-5.268-5.9944z" strokeWidth="1.0095"/> - <path id="path3036" d="m2.607 52.819a9.1058 9.1058 0 0 1 -7.8859 -4.5529 9.1058 9.1058 0 0 1 0 -9.1058 9.1058 9.1058 0 0 1 7.8859 -4.5529l-2e-7 9.1058z" transform="rotate(-30)" strokeWidth="3.6204"/> - <path id="path3038-8" d="m17.502 6.8895c-13.868 8.0065-18.619 25.74-10.612 39.608 8.006 13.868 25.739 18.619 39.608 10.613 13.868-8.007 18.619-25.74 10.613-39.609-8.007-13.868-25.74-18.619-39.609-10.612zm1.706 2.9541c12.237-7.0648 27.884-2.8722 34.948 9.3644 7.065 12.237 2.873 27.884-9.364 34.948-12.237 7.065-27.884 2.873-34.948-9.364-7.0652-12.237-2.8726-27.884 9.364-34.948z" strokeWidth="1.0095"/> +const Logo = ({ fill }) => ( + <svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 0 240 64" width="240" version="1.1"> + <g id="layer1"> + <rect id="rect3778" height="64" width="236.1" y="0" x="0" fill="none" /> + <ellipse id="path3038" rx="28.995" ry="28.995" transform="rotate(-30)" cy="43.713" cx="11.713" strokeWidth="10.699" fill="#fff" /> + <g fill={fill}> + <circle id="path2993" strokeWidth="1.3262" transform="rotate(-30)" cy="43.713" cx="9.4364" r="2.2765" /> + <path id="path3004" d="m37.012 24.177-2.8428 3.6128c0.66345 0.52205 1.3255 1.1576 1.7734 1.9333 0.4479 0.77578 0.66726 1.6669 0.78764 2.5025l4.5502-0.65558c-0.193-1.42-0.633-2.804-1.394-4.123s-1.74-2.391-2.874-3.27z" strokeWidth="1.0095" /> + <path id="path3014" d="m42.504 16.9-2.8428 3.6128c1.607 1.2355 3.0914 2.7935 4.1679 4.6581s1.6835 3.9291 1.95 5.9386l4.5502-0.65558c-0.33967-2.5954-1.1669-5.1513-2.5573-7.5594-1.3903-2.4081-3.1901-4.4025-5.268-5.9944z" strokeWidth="1.0095" /> + <path id="path3036" d="m2.607 52.819a9.1058 9.1058 0 0 1 -7.8859 -4.5529 9.1058 9.1058 0 0 1 0 -9.1058 9.1058 9.1058 0 0 1 7.8859 -4.5529l-2e-7 9.1058z" transform="rotate(-30)" strokeWidth="3.6204" /> + <path id="path3038-8" d="m17.502 6.8895c-13.868 8.0065-18.619 25.74-10.612 39.608 8.006 13.868 25.739 18.619 39.608 10.613 13.868-8.007 18.619-25.74 10.613-39.609-8.007-13.868-25.74-18.619-39.609-10.612zm1.706 2.9541c12.237-7.0648 27.884-2.8722 34.948 9.3644 7.065 12.237 2.873 27.884-9.364 34.948-12.237 7.065-27.884 2.873-34.948-9.364-7.0652-12.237-2.8726-27.884 9.364-34.948z" strokeWidth="1.0095" /> <g id="text3003" ariaLabel="Traccar"> - <path id="path4172" d="m89.719 48.671h-3.915v-30.192h-10.663v-3.4775h25.241v3.4775h-10.663v30.192z"/> - <path id="path4174" d="m116.36 22.969q1.6812 0 3.0169 0.27636l-0.52968 3.5466q-1.566-0.34544-2.7636-0.34544-3.063 0-5.2508 2.4872-2.1648 2.4872-2.1648 6.195v13.541h-3.8229v-25.241h3.1551l0.43756 4.675h0.18424q1.4048-2.4642 3.3854-3.7999t4.3526-1.3357z"/> - <path id="path4176" d="m139.62 48.671-0.75998-3.5926h-0.18424q-1.8884 2.3721-3.7769 3.2242-1.8654 0.82907-4.675 0.82907-3.7538 0-5.8956-1.9345-2.1187-1.9345-2.1187-5.5041 0-7.6459 12.229-8.0143l4.2835-0.13818v-1.566q0-2.9708-1.2897-4.3756-1.2666-1.4278-4.0763-1.4278-3.1551 0-7.1392 1.9345l-1.1745-2.9248q1.8654-1.0133 4.0762-1.589 2.2339-0.57574 4.4678-0.57574 4.5138 0 6.6786 2.0036 2.1878 2.0036 2.1878 6.4253v17.226h-2.8326zm-8.6361-2.6945q3.5696 0 5.5962-1.9575 2.0496-1.9575 2.0496-5.4811v-2.2799l-3.8229 0.16121q-4.5599 0.16121-6.5865 1.4278-2.0036 1.2436-2.0036 3.892 0 2.0727 1.2436 3.1551 1.2666 1.0824 3.5236 1.0824z"/> - <path id="path4178" d="m160.44 49.131q-5.4811 0-8.498-3.3623-2.9939-3.3854-2.9939-9.5573 0-6.3332 3.0399-9.7876 3.063-3.4545 8.7052-3.4545 1.8194 0 3.6387 0.3915t2.8557 0.92119l-1.1745 3.2472q-1.2666-0.50665-2.7636-0.82907-1.4969-0.34544-2.6484-0.34544-7.6919 0-7.6919 9.8106 0 4.652 1.8654 7.1392 1.8884 2.4872 5.5732 2.4872 3.1551 0 6.4713-1.3588v3.3854q-2.5333 1.3127-6.3792 1.3127z"/> - <path id="path4180" d="m182.92 49.131q-5.4811 0-8.498-3.3623-2.9939-3.3854-2.9939-9.5573 0-6.3332 3.0399-9.7876 3.063-3.4545 8.7052-3.4545 1.8193 0 3.6387 0.3915t2.8557 0.92119l-1.1745 3.2472q-1.2666-0.50665-2.7636-0.82907-1.4969-0.34544-2.6484-0.34544-7.6919 0-7.6919 9.8106 0 4.652 1.8654 7.1392 1.8884 2.4872 5.5732 2.4872 3.1551 0 6.4714-1.3588v3.3854q-2.5333 1.3127-6.3792 1.3127z"/> - <path id="path4182" d="m210.83 48.671-0.75998-3.5926h-0.18424q-1.8884 2.3721-3.7769 3.2242-1.8654 0.82907-4.675 0.82907-3.7538 0-5.8956-1.9345-2.1187-1.9345-2.1187-5.5041 0-7.6459 12.229-8.0143l4.2835-0.13818v-1.566q0-2.9708-1.2897-4.3756-1.2666-1.4278-4.0762-1.4278-3.1551 0-7.1392 1.9345l-1.1745-2.9248q1.8654-1.0133 4.0762-1.589 2.2339-0.57574 4.4678-0.57574 4.5138 0 6.6786 2.0036 2.1878 2.0036 2.1878 6.4253v17.226h-2.8326zm-8.6361-2.6945q3.5696 0 5.5962-1.9575 2.0496-1.9575 2.0496-5.4811v-2.2799l-3.8229 0.16121q-4.5599 0.16121-6.5865 1.4278-2.0036 1.2436-2.0036 3.892 0 2.0727 1.2436 3.1551 1.2666 1.0824 3.5235 1.0824z"/> - <path id="path4184" d="m233.08 22.969q1.6812 0 3.0169 0.27636l-0.52968 3.5466q-1.566-0.34544-2.7636-0.34544-3.0629 0-5.2508 2.4872-2.1648 2.4872-2.1648 6.195v13.541h-3.8229v-25.241h3.1551l0.43757 4.675h0.18423q1.4048-2.4642 3.3854-3.7999t4.3526-1.3357z"/> - </g> + <path id="path4172" d="m89.719 48.671h-3.915v-30.192h-10.663v-3.4775h25.241v3.4775h-10.663v30.192z" /> + <path id="path4174" d="m116.36 22.969q1.6812 0 3.0169 0.27636l-0.52968 3.5466q-1.566-0.34544-2.7636-0.34544-3.063 0-5.2508 2.4872-2.1648 2.4872-2.1648 6.195v13.541h-3.8229v-25.241h3.1551l0.43756 4.675h0.18424q1.4048-2.4642 3.3854-3.7999t4.3526-1.3357z" /> + <path id="path4176" d="m139.62 48.671-0.75998-3.5926h-0.18424q-1.8884 2.3721-3.7769 3.2242-1.8654 0.82907-4.675 0.82907-3.7538 0-5.8956-1.9345-2.1187-1.9345-2.1187-5.5041 0-7.6459 12.229-8.0143l4.2835-0.13818v-1.566q0-2.9708-1.2897-4.3756-1.2666-1.4278-4.0763-1.4278-3.1551 0-7.1392 1.9345l-1.1745-2.9248q1.8654-1.0133 4.0762-1.589 2.2339-0.57574 4.4678-0.57574 4.5138 0 6.6786 2.0036 2.1878 2.0036 2.1878 6.4253v17.226h-2.8326zm-8.6361-2.6945q3.5696 0 5.5962-1.9575 2.0496-1.9575 2.0496-5.4811v-2.2799l-3.8229 0.16121q-4.5599 0.16121-6.5865 1.4278-2.0036 1.2436-2.0036 3.892 0 2.0727 1.2436 3.1551 1.2666 1.0824 3.5236 1.0824z" /> + <path id="path4178" d="m160.44 49.131q-5.4811 0-8.498-3.3623-2.9939-3.3854-2.9939-9.5573 0-6.3332 3.0399-9.7876 3.063-3.4545 8.7052-3.4545 1.8194 0 3.6387 0.3915t2.8557 0.92119l-1.1745 3.2472q-1.2666-0.50665-2.7636-0.82907-1.4969-0.34544-2.6484-0.34544-7.6919 0-7.6919 9.8106 0 4.652 1.8654 7.1392 1.8884 2.4872 5.5732 2.4872 3.1551 0 6.4713-1.3588v3.3854q-2.5333 1.3127-6.3792 1.3127z" /> + <path id="path4180" d="m182.92 49.131q-5.4811 0-8.498-3.3623-2.9939-3.3854-2.9939-9.5573 0-6.3332 3.0399-9.7876 3.063-3.4545 8.7052-3.4545 1.8193 0 3.6387 0.3915t2.8557 0.92119l-1.1745 3.2472q-1.2666-0.50665-2.7636-0.82907-1.4969-0.34544-2.6484-0.34544-7.6919 0-7.6919 9.8106 0 4.652 1.8654 7.1392 1.8884 2.4872 5.5732 2.4872 3.1551 0 6.4714-1.3588v3.3854q-2.5333 1.3127-6.3792 1.3127z" /> + <path id="path4182" d="m210.83 48.671-0.75998-3.5926h-0.18424q-1.8884 2.3721-3.7769 3.2242-1.8654 0.82907-4.675 0.82907-3.7538 0-5.8956-1.9345-2.1187-1.9345-2.1187-5.5041 0-7.6459 12.229-8.0143l4.2835-0.13818v-1.566q0-2.9708-1.2897-4.3756-1.2666-1.4278-4.0762-1.4278-3.1551 0-7.1392 1.9345l-1.1745-2.9248q1.8654-1.0133 4.0762-1.589 2.2339-0.57574 4.4678-0.57574 4.5138 0 6.6786 2.0036 2.1878 2.0036 2.1878 6.4253v17.226h-2.8326zm-8.6361-2.6945q3.5696 0 5.5962-1.9575 2.0496-1.9575 2.0496-5.4811v-2.2799l-3.8229 0.16121q-4.5599 0.16121-6.5865 1.4278-2.0036 1.2436-2.0036 3.892 0 2.0727 1.2436 3.1551 1.2666 1.0824 3.5235 1.0824z" /> + <path id="path4184" d="m233.08 22.969q1.6812 0 3.0169 0.27636l-0.52968 3.5466q-1.566-0.34544-2.7636-0.34544-3.0629 0-5.2508 2.4872-2.1648 2.4872-2.1648 6.195v13.541h-3.8229v-25.241h3.1551l0.43757 4.675h0.18423q1.4048-2.4642 3.3854-3.7999t4.3526-1.3357z" /> </g> - </g> - </svg> - ) -} + </g> + </g> + </svg> +); export default Logo; diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js index 8d0b18d2..88608df7 100644 --- a/modern/src/MainPage.js +++ b/modern/src/MainPage.js @@ -11,7 +11,7 @@ import GeofenceMap from './map/GeofenceMap'; import CurrentPositionsMap from './map/CurrentPositionsMap'; import CurrentLocationMap from './map/CurrentLocationMap'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { height: '100%', display: 'flex', @@ -24,7 +24,7 @@ const useStyles = makeStyles(theme => ({ flexDirection: 'row', [theme.breakpoints.down('xs')]: { flexDirection: 'column-reverse', - } + }, }, drawerPaper: { position: 'relative', @@ -50,8 +50,9 @@ const MainPage = ({ width }) => { <div className={classes.content}> <Drawer anchor={isWidthUp('sm', width) ? 'left' : 'bottom'} - variant='permanent' - classes={{ paper: classes.drawerPaper }}> + variant="permanent" + classes={{ paper: classes.drawerPaper }} + > <DevicesList /> </Drawer> <div className={classes.mapContainer}> @@ -68,6 +69,6 @@ const MainPage = ({ width }) => { </div> </div> ); -} +}; export default withWidth()(MainPage); diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js index 064507fb..da7c42c5 100644 --- a/modern/src/MainToolbar.js +++ b/modern/src/MainToolbar.js @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { useHistory } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; import { useDispatch, useSelector } from 'react-redux'; -import { sessionActions } from './store'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; @@ -27,17 +26,18 @@ import FolderIcon from '@material-ui/icons/Folder'; import CreateIcon from '@material-ui/icons/Create'; import ReplayIcon from '@material-ui/icons/Replay'; import BuildIcon from '@material-ui/icons/Build'; +import { sessionActions } from './store'; import t from './common/localization'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ flex: { - flexGrow: 1 + flexGrow: 1, }, appBar: { zIndex: theme.zIndex.drawer + 1, }, list: { - width: 250 + width: 250, }, menuButton: { marginLeft: -12, @@ -50,11 +50,11 @@ const MainToolbar = () => { const [drawer, setDrawer] = useState(false); const classes = useStyles(); const history = useHistory(); - const adminEnabled = useSelector(state => state.session.user && state.session.user.administrator); - const userId = useSelector(state => state.session.user && state.session.user.id); + const adminEnabled = useSelector((state) => state.session.user && state.session.user.administrator); + const userId = useSelector((state) => state.session.user && state.session.user.id); - const openDrawer = () => { setDrawer(true) } - const closeDrawer = () => { setDrawer(false) } + const openDrawer = () => { setDrawer(true); }; + const closeDrawer = () => { setDrawer(false); }; const handleLogout = async () => { const response = await fetch('/api/session', { method: 'DELETE' }); @@ -62,7 +62,7 @@ const MainToolbar = () => { dispatch(sessionActions.updateUser(null)); history.push('/login'); } - } + }; return ( <> @@ -71,7 +71,8 @@ const MainToolbar = () => { <IconButton className={classes.menuButton} color="inherit" - onClick={openDrawer}> + onClick={openDrawer} + > <MenuIcon /> </IconButton> <Typography variant="h6" color="inherit" className={classes.flex}> @@ -86,7 +87,8 @@ const MainToolbar = () => { className={classes.list} role="button" onClick={closeDrawer} - onKeyDown={closeDrawer}> + onKeyDown={closeDrawer} + > <List> <ListItem button onClick={() => history.push('/')}> <ListItemIcon> @@ -105,15 +107,16 @@ const MainToolbar = () => { <DescriptionIcon /> </ListItemIcon> <ListItemText primary={t('reportTitle')} /> - </ListItem> + </ListItem> </List> <Divider /> <List - subheader={ + subheader={( <ListSubheader> {t('settingsTitle')} </ListSubheader> - }> + )} + > <ListItem button disabled={!userId} onClick={() => history.push(`/user/${userId}`)}> <ListItemIcon> <PersonIcon /> @@ -161,11 +164,12 @@ const MainToolbar = () => { <> <Divider /> <List - subheader={ + subheader={( <ListSubheader> {t('userAdmin')} </ListSubheader> - }> + )} + > <ListItem button onClick={() => history.push('/admin/server')}> <ListItemIcon> <StorageIcon /> @@ -191,6 +195,6 @@ const MainToolbar = () => { </Drawer> </> ); -} +}; export default MainToolbar; diff --git a/modern/src/PositionPage.js b/modern/src/PositionPage.js index a91a7a15..ce4c4ac2 100644 --- a/modern/src/PositionPage.js +++ b/modern/src/PositionPage.js @@ -1,14 +1,16 @@ import React, { Fragment, useState } from 'react'; -import t from './common/localization'; -import { makeStyles, Typography, ListItem, ListItemText, ListItemSecondaryAction, List, Container, Paper, Divider } from '@material-ui/core'; +import { + makeStyles, Typography, ListItem, ListItemText, ListItemSecondaryAction, List, Container, Paper, Divider, +} from '@material-ui/core'; import { useParams } from 'react-router-dom'; +import t from './common/localization'; import { useEffectAsync } from './reactHelper'; import MainToolbar from './MainToolbar'; import { formatPosition } from './common/formatter'; import { prefixString } from './common/stringUtils'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), @@ -26,7 +28,7 @@ const PositionPage = () => { if (id) { const response = await fetch(`/api/positions?id=${id}`, { headers: { - 'Accept': 'application/json' + Accept: 'application/json', }, }); if (response.ok) { @@ -38,28 +40,27 @@ const PositionPage = () => { } }, [id]); - const formatKey = (key) => { - return t(prefixString('position', key)) || `${t('sharedAttribute')} "${key}"`; - }; + const formatKey = (key) => t(prefixString('position', key)) || `${t('sharedAttribute')} "${key}"`; const attributesList = () => { - const combinedList = {...item, ...item.attributes}; - return Object.entries(combinedList).filter(([_, value]) => typeof value !== 'object'); - } + const combinedList = { ...item, ...item.attributes }; + return Object.entries(combinedList).filter(([, value]) => typeof value !== 'object'); + }; return ( <> <MainToolbar /> - <Container maxWidth='sm' className={classes.root}> + <Container maxWidth="sm" className={classes.root}> <Paper> - {item && + {item + && ( <List> {attributesList().map(([key, value], index, list) => ( <Fragment key={key}> <ListItem> <ListItemText primary={formatKey(key)} - /> + /> <ListItemSecondaryAction> <Typography variant="body2"> {formatPosition(value, key)} @@ -70,11 +71,11 @@ const PositionPage = () => { </Fragment> ))} </List> - } + )} </Paper> </Container> </> ); -} +}; export default PositionPage; diff --git a/modern/src/RegisterDialog.js b/modern/src/RegisterDialog.js index c640c515..aafec66b 100644 --- a/modern/src/RegisterDialog.js +++ b/modern/src/RegisterDialog.js @@ -1,4 +1,3 @@ -import t from './common/localization' import React, { useState } from 'react'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; @@ -7,6 +6,7 @@ import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import TextField from '@material-ui/core/TextField'; import Snackbar from '@material-ui/core/Snackbar'; +import t from './common/localization'; const RegisterDialog = ({ showDialog, onResult }) => { const [name, setName] = useState(''); @@ -14,81 +14,81 @@ const RegisterDialog = ({ showDialog, onResult }) => { const [password, setPassword] = useState(''); const [snackbarOpen, setSnackbarOpen] = useState(false); - const submitDisabled = () => { - return !name || !/(.+)@(.+)\.(.{2,})/.test(email) || !password; - } + const submitDisabled = () => !name || !/(.+)@(.+)\.(.{2,})/.test(email) || !password; const handleRegister = async () => { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({name, email, password}) + body: JSON.stringify({ name, email, password }), }); if (response.ok) { showDialog = false; setSnackbarOpen(true); - } - } + } + }; if (snackbarOpen) { - return ( <Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={snackbarOpen} autoHideDuration={6000} - onClose={() => { onResult(true) }} - message={t('loginCreated')} /> + onClose={() => { onResult(true); }} + message={t('loginCreated')} + /> ); - - } else if (showDialog) { - + } if (showDialog) { return ( <Dialog - open={true} - onClose={() => { onResult(false) }}> + open + onClose={() => { onResult(false); }} + > <DialogContent> <DialogContentText>{t('loginRegister')}</DialogContentText> <TextField - margin='normal' + margin="normal" required fullWidth label={t('sharedName')} - name='name' + name="name" value={name || ''} - autoComplete='name' + autoComplete="name" autoFocus - onChange={event => setName(event.target.value)} /> + onChange={(event) => setName(event.target.value)} + /> <TextField - margin='normal' + margin="normal" required fullWidth - type='email' + type="email" label={t('userEmail')} - name='email' + name="email" value={email || ''} - autoComplete='email' - onChange={event => setEmail(event.target.value)} /> + autoComplete="email" + onChange={(event) => setEmail(event.target.value)} + /> <TextField - margin='normal' + margin="normal" required fullWidth label={t('userPassword')} - name='password' + name="password" value={password || ''} - type='password' - autoComplete='current-password' - onChange={event => setPassword(event.target.value)} /> + type="password" + autoComplete="current-password" + onChange={(event) => setPassword(event.target.value)} + /> </DialogContent> <DialogActions> <Button color="primary" onClick={handleRegister} disabled={submitDisabled()}>{t('loginRegister')}</Button> <Button autoFocus onClick={() => onResult(false)}>{t('sharedCancel')}</Button> </DialogActions> </Dialog> - ) - + ); } + return null; }; export default RegisterDialog; diff --git a/modern/src/RemoveDialog.js b/modern/src/RemoveDialog.js index bbcfb226..8ff162c2 100644 --- a/modern/src/RemoveDialog.js +++ b/modern/src/RemoveDialog.js @@ -1,12 +1,14 @@ -import t from './common/localization' import React from 'react'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; +import t from './common/localization'; -const RemoveDialog = ({ open, endpoint, itemId, onResult }) => { +const RemoveDialog = ({ + open, endpoint, itemId, onResult, +}) => { const handleRemove = async () => { const response = await fetch(`/api/${endpoint}/${itemId}`, { method: 'DELETE' }); if (response.ok) { @@ -17,7 +19,8 @@ const RemoveDialog = ({ open, endpoint, itemId, onResult }) => { return ( <Dialog open={open} - onClose={() => { onResult(false) }}> + onClose={() => { onResult(false); }} + > <DialogContent> <DialogContentText>{t('sharedRemoveConfirm')}</DialogContentText> </DialogContent> diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js index cda693a1..ae82d134 100644 --- a/modern/src/SocketController.js +++ b/modern/src/SocketController.js @@ -1,19 +1,19 @@ -import { useDispatch, useSelector } from 'react-redux'; -import { connect } from 'react-redux'; -import { positionsActions, devicesActions, sessionActions } from './store'; +import { useDispatch, useSelector, connect } from 'react-redux'; + import { useHistory } from 'react-router-dom'; +import { positionsActions, devicesActions, sessionActions } from './store'; import { useEffectAsync } from './reactHelper'; -const displayNotifications = events => { - if ("Notification" in window) { - if (Notification.permission === "granted") { - for (const event of events) { +const displayNotifications = (events) => { + if ('Notification' in window) { + if (Notification.permission === 'granted') { + events.forEach((event) => { const notification = new Notification(`Event: ${event.type}`); setTimeout(notification.close.bind(notification), 4 * 1000); - } - } else if (Notification.permission !== "denied") { - Notification.requestPermission(permission => { - if (permission === "granted") { + }); + } else if (Notification.permission !== 'denied') { + Notification.requestPermission((permission) => { + if (permission === 'granted') { displayNotifications(events); } }); @@ -24,11 +24,11 @@ const displayNotifications = events => { const SocketController = () => { const dispatch = useDispatch(); const history = useHistory(); - const authenticated = useSelector(state => !!state.session.user); + const authenticated = useSelector((state) => !!state.session.user); const connectSocket = () => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const socket = new WebSocket(protocol + '//' + window.location.host + '/api/socket'); + const socket = new WebSocket(`${protocol}//${window.location.host}/api/socket`); socket.onclose = () => { setTimeout(() => connectSocket(), 60 * 1000); @@ -46,7 +46,7 @@ const SocketController = () => { displayNotifications(data.events); } }; - } + }; useEffectAsync(async () => { const response = await fetch('/api/server'); @@ -73,6 +73,6 @@ const SocketController = () => { }, [authenticated]); return null; -} +}; export default connect()(SocketController); diff --git a/modern/src/StartPage.js b/modern/src/StartPage.js index 0a086179..3edc5b31 100644 --- a/modern/src/StartPage.js +++ b/modern/src/StartPage.js @@ -2,7 +2,7 @@ import React from 'react'; import { useMediaQuery, makeStyles, Paper } from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { display: 'flex', height: '100vh', @@ -23,20 +23,20 @@ const useStyles = makeStyles(theme => ({ }, }, paper: { - display:'flex', + display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1, boxShadow: '-2px 0px 16px rgba(0, 0, 0, 0.25)', [theme.breakpoints.up('lg')]: { - padding: theme.spacing(0, 25, 0, 0) + padding: theme.spacing(0, 25, 0, 0), }, }, form: { maxWidth: theme.spacing(52), padding: theme.spacing(5), - width: "100%", + width: '100%', }, })); @@ -47,11 +47,12 @@ const StartPage = ({ children }) => { return ( <main className={classes.root}> <div className={classes.sidebar}> - {!useMediaQuery(theme.breakpoints.down('md')) && + {!useMediaQuery(theme.breakpoints.down('md')) + && ( <svg height="64" width="240"> - <use xlinkHref="/logo.svg#img"></use> + <use xlinkHref="/logo.svg#img" /> </svg> - } + )} </div> <Paper className={classes.paper}> <form className={classes.form}> @@ -59,7 +60,7 @@ const StartPage = ({ children }) => { </form> </Paper> </main> - ) -} + ); +}; export default StartPage; diff --git a/modern/src/UserPage.js b/modern/src/UserPage.js index dfe8b982..6afbdf7e 100644 --- a/modern/src/UserPage.js +++ b/modern/src/UserPage.js @@ -1,11 +1,13 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from './common/localization'; import userAttributes from './attributes/userAttributes'; import EditItemView from './EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditAttributesView from './attributes/EditAttributesView'; import LinkField from './form/LinkField'; @@ -22,7 +24,8 @@ const UserPage = () => { return ( <EditItemView endpoint="users" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -34,21 +37,24 @@ const UserPage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.email || ''} - onChange={event => setItem({...item, email: event.target.value})} + onChange={(event) => setItem({ ...item, email: event.target.value })} label={t('userEmail')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" type="password" - onChange={event => setItem({...item, password: event.target.value})} + onChange={(event) => setItem({ ...item, password: event.target.value })} label={t('userPassword')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -60,12 +66,13 @@ const UserPage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={userAttributes} - /> + /> </AccordionDetails> </Accordion> - {item.id && + {item.id + && ( <Accordion> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="subtitle1"> @@ -76,28 +83,30 @@ const UserPage = () => { <LinkField margin="normal" endpointAll="/api/devices?all=true" - endpointLinked={"/api/devices?userId=" + item.id} + endpointLinked={`/api/devices?userId=${item.id}`} baseId={item.id} keyBase="userId" keyLink="deviceId" label={t('deviceTitle')} - variant="filled" /> + variant="filled" + /> <LinkField margin="normal" endpointAll="/api/groups?all=true" - endpointLinked={"/api/groups?userId=" + item.id} + endpointLinked={`/api/groups?userId=${item.id}`} baseId={item.id} keyBase="userId" keyLink="groupId" label={t('settingsGroups')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> - } + )} </> - } + )} </EditItemView> ); -} +}; export default UserPage; diff --git a/modern/src/admin/ServerPage.js b/modern/src/admin/ServerPage.js index 43664e54..7decac1a 100644 --- a/modern/src/admin/ServerPage.js +++ b/modern/src/admin/ServerPage.js @@ -1,18 +1,20 @@ import React from 'react'; import TextField from '@material-ui/core/TextField'; -import t from '../common/localization'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Button, FormControl, Container, Checkbox, FormControlLabel } from '@material-ui/core'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, Button, FormControl, Container, Checkbox, FormControlLabel, +} from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; +import t from '../common/localization'; import MainToolbar from '../MainToolbar'; import { sessionActions } from '../store'; import EditAttributesView from '../attributes/EditAttributesView'; import deviceAttributes from '../attributes/deviceAttributes'; import userAttributes from '../attributes/userAttributes'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { marginTop: theme.spacing(2), }, @@ -33,7 +35,7 @@ const ServerPage = () => { const dispatch = useDispatch(); const classes = useStyles(); - const item = useSelector(state => state.session.server); + const item = useSelector((state) => state.session.server); const setItem = (updatedItem) => dispatch(sessionActions.updateServer(updatedItem)); const handleSave = async () => { @@ -51,8 +53,9 @@ const ServerPage = () => { return ( <> <MainToolbar /> - <Container maxWidth='xs' className={classes.container}> - {item && + <Container maxWidth="xs" className={classes.container}> + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -64,9 +67,10 @@ const ServerPage = () => { <TextField margin="normal" value={item.announcement || ''} - onChange={event => setItem({...item, announcement: event.target.value})} + onChange={(event) => setItem({ ...item, announcement: event.target.value })} label={t('serverAnnouncement')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -77,17 +81,21 @@ const ServerPage = () => { </AccordionSummary> <AccordionDetails className={classes.details}> <FormControlLabel - control={<Checkbox checked={item.registration} onChange={event => setItem({...item, registration: event.target.checked})} />} - label={t('serverRegistration')} /> + control={<Checkbox checked={item.registration} onChange={(event) => setItem({ ...item, registration: event.target.checked })} />} + label={t('serverRegistration')} + /> <FormControlLabel - control={<Checkbox checked={item.readonly} onChange={event => setItem({...item, readonly: event.target.checked})} />} - label={t('serverReadonly')} /> + control={<Checkbox checked={item.readonly} onChange={(event) => setItem({ ...item, readonly: event.target.checked })} />} + label={t('serverReadonly')} + /> <FormControlLabel - control={<Checkbox checked={item.deviceReadonly} onChange={event => setItem({...item, deviceReadonly: event.target.checked})} />} - label={t('userDeviceReadonly')} /> + control={<Checkbox checked={item.deviceReadonly} onChange={(event) => setItem({ ...item, deviceReadonly: event.target.checked })} />} + label={t('userDeviceReadonly')} + /> <FormControlLabel - control={<Checkbox checked={item.limitCommands} onChange={event => setItem({...item, limitCommands: event.target.checked})} />} - label={t('userLimitCommands')} /> + control={<Checkbox checked={item.limitCommands} onChange={(event) => setItem({ ...item, limitCommands: event.target.checked })} />} + label={t('userLimitCommands')} + /> </AccordionDetails> </Accordion> <Accordion> @@ -99,19 +107,19 @@ const ServerPage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} - definitions={{...userAttributes, ...deviceAttributes}} - /> + setAttributes={(attributes) => setItem({ ...item, attributes })} + definitions={{ ...userAttributes, ...deviceAttributes }} + /> </AccordionDetails> </Accordion> </> - } - <FormControl fullWidth margin='normal'> + )} + <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={() => history.goBack()}> {t('sharedCancel')} </Button> - <Button type='button' color='primary' variant='contained' onClick={handleSave}> + <Button type="button" color="primary" variant="contained" onClick={handleSave}> {t('sharedSave')} </Button> </div> @@ -119,6 +127,6 @@ const ServerPage = () => { </Container> </> ); -} +}; export default ServerPage; diff --git a/modern/src/admin/StatisticsPage.js b/modern/src/admin/StatisticsPage.js index 1e440a46..131ab7e4 100644 --- a/modern/src/admin/StatisticsPage.js +++ b/modern/src/admin/StatisticsPage.js @@ -1,10 +1,11 @@ - import React, { useState } from 'react'; -import { FormControl, InputLabel,Select, MenuItem, TextField, Button, TableContainer, Table, TableRow, TableCell, TableHead, TableBody, Paper } from '@material-ui/core'; +import { + FormControl, InputLabel, Select, MenuItem, TextField, Button, TableContainer, Table, TableRow, TableCell, TableHead, TableBody, Paper, +} from '@material-ui/core'; +import moment from 'moment'; import t from '../common/localization'; import { formatDate } from '../common/formatter'; import ReportLayoutPage from '../reports/ReportLayoutPage'; -import moment from 'moment'; const Filter = ({ setItems }) => { const [period, setPeriod] = useState('today'); @@ -50,7 +51,7 @@ const Filter = ({ setItems }) => { if (response.ok) { setItems(await response.json()); } - } + }; return ( <> @@ -73,8 +74,9 @@ const Filter = ({ setItems }) => { label={t('reportFrom')} type="datetime-local" value={from.format(moment.HTML5_FMT.DATETIME_LOCAL)} - onChange={e => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} - fullWidth /> + onChange={(e) => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} + fullWidth + /> )} {period === 'custom' && ( <TextField @@ -83,16 +85,16 @@ const Filter = ({ setItems }) => { label={t('reportTo')} type="datetime-local" value={to.format(moment.HTML5_FMT.DATETIME_LOCAL)} - onChange={e => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} - fullWidth /> + onChange={(e) => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} + fullWidth + /> )} <Button variant="contained" color="primary" onClick={handleClick} fullWidth>{t('reportShow')}</Button> - </> - ) -} + </> + ); +}; const StatisticsPage = () => { - const [items, setItems] = useState([]); return ( @@ -125,7 +127,7 @@ const StatisticsPage = () => { <TableCell>{item.mailSent}</TableCell> <TableCell>{item.smsSent}</TableCell> <TableCell>{item.geocoderRequests}</TableCell> - <TableCell>{item.geolocationRequests}</TableCell> + <TableCell>{item.geolocationRequests}</TableCell> </TableRow> ))} </TableBody> @@ -133,6 +135,6 @@ const StatisticsPage = () => { </TableContainer> </ReportLayoutPage> ); -} +}; export default StatisticsPage; diff --git a/modern/src/admin/UsersPage.js b/modern/src/admin/UsersPage.js index 630bea43..fc6e6736 100644 --- a/modern/src/admin/UsersPage.js +++ b/modern/src/admin/UsersPage.js @@ -1,13 +1,15 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; import { formatBoolean } from '../common/formatter'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -28,43 +30,41 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => { 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 onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.email}</TableCell> - <TableCell>{formatBoolean(item, 'administrator')}</TableCell> - <TableCell>{formatBoolean(item, 'disabled')}</TableCell> + <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> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.email}</TableCell> + <TableCell>{formatBoolean(item, 'administrator')}</TableCell> + <TableCell>{formatBoolean(item, 'disabled')}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const UsersPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={UsersView} editPath="/user" endpoint="users" /> - </> - ); -} +const UsersPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={UsersView} editPath="/user" endpoint="users" /> + </> +); export default UsersPage; diff --git a/modern/src/attributes/AddAttributeDialog.js b/modern/src/attributes/AddAttributeDialog.js index ee4c48c1..24c208a5 100644 --- a/modern/src/attributes/AddAttributeDialog.js +++ b/modern/src/attributes/AddAttributeDialog.js @@ -1,12 +1,14 @@ import React, { useState } from 'react'; -import { Button, Dialog, DialogActions, DialogContent, FormControl, InputLabel, MenuItem, Select, TextField } from "@material-ui/core"; +import { + Button, Dialog, DialogActions, DialogContent, FormControl, InputLabel, MenuItem, Select, TextField, +} from '@material-ui/core'; -import t from '../common/localization'; import { Autocomplete, createFilterOptions } from '@material-ui/lab'; +import t from '../common/localization'; const AddAttributeDialog = ({ open, onResult, definitions }) => { const filter = createFilterOptions({ - stringify: option => option.name, + stringify: (option) => option.name, }); const options = Object.entries(definitions).map(([key, value]) => ({ @@ -39,10 +41,8 @@ const AddAttributeDialog = ({ open, onResult, definitions }) => { return filtered; }} options={options} - getOptionLabel={option => { - return option && typeof option === 'object' ? option.name : option; - }} - renderOption={option => option.name} + getOptionLabel={(option) => (option && typeof option === 'object' ? option.name : option)} + renderOption={(option) => option.name} freeSolo renderInput={(params) => ( <TextField {...params} label={t('sharedAttribute')} variant="filled" margin="normal" /> @@ -52,14 +52,16 @@ const AddAttributeDialog = ({ open, onResult, definitions }) => { variant="filled" margin="normal" fullWidth - disabled={key in definitions}> + disabled={key in definitions} + > <InputLabel>{t('sharedType')}</InputLabel> <Select value={type} - onChange={e => setType(e.target.value)}> - <MenuItem value={'string'}>{t('sharedTypeString')}</MenuItem> - <MenuItem value={'number'}>{t('sharedTypeNumber')}</MenuItem> - <MenuItem value={'boolean'}>{t('sharedTypeBoolean')}</MenuItem> + onChange={(e) => setType(e.target.value)} + > + <MenuItem value="string">{t('sharedTypeString')}</MenuItem> + <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem> + <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem> </Select> </FormControl> </DialogContent> @@ -67,17 +69,19 @@ const AddAttributeDialog = ({ open, onResult, definitions }) => { <Button color="primary" disabled={!key} - onClick={() => onResult({ key, type })}> + onClick={() => onResult({ key, type })} + > {t('sharedAdd')} </Button> <Button autoFocus - onClick={() => onResult(null)}> + onClick={() => onResult(null)} + > {t('sharedCancel')} </Button> </DialogActions> </Dialog> - ) -} + ); +}; export default AddAttributeDialog; diff --git a/modern/src/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js index 619d857c..e38c02ae 100644 --- a/modern/src/attributes/EditAttributesView.js +++ b/modern/src/attributes/EditAttributesView.js @@ -1,13 +1,14 @@ import React, { useState } from 'react'; -import t from '../common/localization'; - -import { Button, Checkbox, FilledInput, FormControl, FormControlLabel, Grid, IconButton, InputAdornment, InputLabel, makeStyles } from "@material-ui/core"; +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'; +import t from '../common/localization'; import AddAttributeDialog from './AddAttributeDialog'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ addButton: { marginTop: theme.spacing(2), marginBottom: theme.spacing(1), @@ -22,10 +23,36 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { const [addDialogShown, setAddDialogShown] = useState(false); + const updateAttribute = (key, value) => { + const updatedAttributes = { ...attributes }; + updatedAttributes[key] = value; + setAttributes(updatedAttributes); + }; + + const deleteAttribute = (key) => { + const updatedAttributes = { ...attributes }; + delete updatedAttributes[key]; + setAttributes(updatedAttributes); + }; + + const getAttributeName = (key) => { + const definition = definitions[key]; + return definition ? definition.name : key; + }; + + const getAttributeType = (value) => { + if (typeof value === 'number') { + return 'number'; + } if (typeof value === 'boolean') { + return 'boolean'; + } + return 'string'; + }; + const convertToList = (attributes) => { - let booleanList = []; - let otherList = []; - for (const key in attributes) { + const booleanList = []; + const otherList = []; + Object.keys(attributes).forEach((key) => { const value = attributes[key]; const type = getAttributeType(value); if (type === 'boolean') { @@ -33,14 +60,14 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { } else { otherList.push({ key, value, type }); } - } + }); return otherList.concat(booleanList); - } + }; const handleAddResult = (definition) => { setAddDialogShown(false); if (definition) { - switch(definition.type) { + switch (definition.type) { case 'number': updateAttribute(definition.key, 0); break; @@ -48,37 +75,10 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { updateAttribute(definition.key, false); break; default: - updateAttribute(definition.key, ""); + updateAttribute(definition.key, ''); break; } } - } - - const updateAttribute = (key, value) => { - let updatedAttributes = {...attributes}; - updatedAttributes[key] = value; - setAttributes(updatedAttributes); - }; - - const deleteAttribute = (key) => { - let updatedAttributes = {...attributes}; - delete updatedAttributes[key]; - setAttributes(updatedAttributes); - }; - - const getAttributeName = (key) => { - const definition = definitions[key]; - return definition ? definition.name : key; - }; - - const getAttributeType = (value) => { - if (typeof value === 'number') { - return 'number'; - } else if (typeof value === 'boolean') { - return 'boolean'; - } else { - return 'string'; - } }; return ( @@ -88,37 +88,37 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { return ( <Grid container direction="row" justify="space-between"> <FormControlLabel - control={ + control={( <Checkbox checked={value} - onChange={e => updateAttribute(key, e.target.checked)} - /> - } - label={getAttributeName(key)} /> + onChange={(e) => updateAttribute(key, e.target.checked)} + /> + )} + label={getAttributeName(key)} + /> <IconButton className={classes.removeButton} onClick={() => deleteAttribute(key)}> <CloseIcon /> </IconButton> </Grid> ); - } else { - return ( - <FormControl variant="filled" margin="normal" key={key}> - <InputLabel>{getAttributeName(key)}</InputLabel> - <FilledInput - type={type === 'number' ? 'number' : 'text'} - value={value || ''} - onChange={e => updateAttribute(key, e.target.value)} - endAdornment={ - <InputAdornment position="end"> - <IconButton onClick={() => deleteAttribute(key)}> - <CloseIcon /> - </IconButton> - </InputAdornment> - } - /> - </FormControl> - ); } + return ( + <FormControl variant="filled" margin="normal" key={key}> + <InputLabel>{getAttributeName(key)}</InputLabel> + <FilledInput + type={type === 'number' ? 'number' : 'text'} + value={value || ''} + onChange={(e) => updateAttribute(key, e.target.value)} + endAdornment={( + <InputAdornment position="end"> + <IconButton onClick={() => deleteAttribute(key)}> + <CloseIcon /> + </IconButton> + </InputAdornment> + )} + /> + </FormControl> + ); })} <Button size="large" @@ -126,16 +126,17 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { color="primary" onClick={() => setAddDialogShown(true)} startIcon={<AddIcon />} - className={classes.addButton}> + className={classes.addButton} + > {t('sharedAdd')} </Button> <AddAttributeDialog open={addDialogShown} onResult={handleAddResult} definitions={definitions} - /> + /> </> ); -} +}; export default EditAttributesView; diff --git a/modern/src/attributes/deviceAttributes.js b/modern/src/attributes/deviceAttributes.js index 891a225e..26e3d928 100644 --- a/modern/src/attributes/deviceAttributes.js +++ b/modern/src/attributes/deviceAttributes.js @@ -1,7 +1,7 @@ -import t from '../common/localization' +import t from '../common/localization'; export default { - 'speedLimit': { + speedLimit: { name: t('attributeSpeedLimit'), type: 'string', }, diff --git a/modern/src/attributes/geofenceAttributes.js b/modern/src/attributes/geofenceAttributes.js index 4b9c9e63..59a8869d 100644 --- a/modern/src/attributes/geofenceAttributes.js +++ b/modern/src/attributes/geofenceAttributes.js @@ -1,7 +1,7 @@ -import t from '../common/localization' +import t from '../common/localization'; export default { - 'speedLimit': { + speedLimit: { name: t('attributeSpeedLimit'), type: 'string', }, diff --git a/modern/src/attributes/positionAttributes.js b/modern/src/attributes/positionAttributes.js index b1efe3df..94f396a1 100644 --- a/modern/src/attributes/positionAttributes.js +++ b/modern/src/attributes/positionAttributes.js @@ -1,19 +1,19 @@ -import t from '../common/localization' +import t from '../common/localization'; export default { - 'raw': { + raw: { name: t('positionRaw'), type: 'string', }, - 'index': { + index: { name: t('positionIndex'), type: 'number', }, - 'ignition': { + ignition: { name: t('positionIgnition'), type: 'boolean', }, - 'odometer': { + odometer: { name: t('positionOdometer'), type: 'number', dataType: 'distance', diff --git a/modern/src/attributes/userAttributes.js b/modern/src/attributes/userAttributes.js index bcec29f2..6f842f91 100644 --- a/modern/src/attributes/userAttributes.js +++ b/modern/src/attributes/userAttributes.js @@ -1,7 +1,7 @@ -import t from '../common/localization' +import t from '../common/localization'; export default { - 'notificationTokens': { + notificationTokens: { name: t('attributeNotificationTokens'), type: 'string', }, @@ -49,19 +49,19 @@ export default { name: t('attributeUiHidePositionAttributes'), type: 'string', }, - 'distanceUnit': { + distanceUnit: { name: t('settingsDistanceUnit'), type: 'string', }, - 'speedUnit': { + speedUnit: { name: t('settingsSpeedUnit'), type: 'string', }, - 'volumeUnit': { + volumeUnit: { name: t('settingsVolumeUnit'), type: 'string', }, - 'timezone': { + timezone: { name: t('sharedTimezone'), type: 'string', }, diff --git a/modern/src/common/converter.js b/modern/src/common/converter.js index 9cdc6239..d45db5d6 100644 --- a/modern/src/common/converter.js +++ b/modern/src/common/converter.js @@ -1,4 +1,4 @@ -const speedConverter = unit => { +const speedConverter = (unit) => { switch (unit) { case 'kmh': return 1.852; @@ -10,7 +10,7 @@ const speedConverter = unit => { } }; -const distanceConverter = unit => { +const distanceConverter = (unit) => { switch (unit) { case 'mi': return 0.000621371; @@ -19,21 +19,13 @@ const distanceConverter = unit => { case 'km': default: return 0.001; - } -} + } +}; -export const speedFromKnots = (value, unit) => { - return value * speedConverter(unit); -} +export const speedFromKnots = (value, unit) => value * speedConverter(unit); -export const speedToKnots = (value, unit) => { - return value / speedConverter(unit); -} +export const speedToKnots = (value, unit) => value / speedConverter(unit); -export const distanceFromMeters = (value, unit) => { - return value * distanceConverter(unit); -} +export const distanceFromMeters = (value, unit) => value * distanceConverter(unit); -export const distanceToMeters = (value, unit) => { - return value / distanceConverter(unit); -} +export const distanceToMeters = (value, unit) => value / distanceConverter(unit); diff --git a/modern/src/common/deviceCategories.js b/modern/src/common/deviceCategories.js index 1eda1902..f5d749aa 100644 --- a/modern/src/common/deviceCategories.js +++ b/modern/src/common/deviceCategories.js @@ -1,4 +1,4 @@ -export const deviceCategories = [ +export default [ 'default', 'animal', 'bicycle', diff --git a/modern/src/common/formatter.js b/modern/src/common/formatter.js index 0d8ac4c9..3c0341b7 100644 --- a/modern/src/common/formatter.js +++ b/modern/src/common/formatter.js @@ -1,5 +1,11 @@ import moment from 'moment'; -import t from '../common/localization'; +import t from './localization'; + +export const formatBoolean = (value) => (value ? t('sharedYes') : t('sharedNo')); + +export const formatNumber = (value, precision = 1) => Number(value.toFixed(precision)); + +export const formatDate = (value, format = 'YYYY-MM-DD HH:mm') => moment(value).format(format); export const formatPosition = (value, key) => { if (value != null && typeof value === 'object') { @@ -18,30 +24,17 @@ export const formatPosition = (value, key) => { case 'course': return value.toFixed(1); case 'batteryLevel': - return value + '%'; + return `${value}%`; default: if (typeof value === 'number') { return formatNumber(value); - } else if (typeof value === 'boolean') { + } if (typeof value === 'boolean') { return formatBoolean(value); - } else { - return value; } + return value; } }; -export const formatBoolean = (value) => { - return value ? t('sharedYes') : t('sharedNo'); -}; - -export const formatNumber = (value, precision = 1) => { - return Number(value.toFixed(precision)); -}; - -export const formatDate = (value, format = 'YYYY-MM-DD HH:mm') => { - return moment(value).format(format); -}; - export const formatDistance = (value, unit) => { switch (unit) { case 'mi': @@ -50,7 +43,7 @@ export const formatDistance = (value, unit) => { return `${(value * 0.000539957).toFixed(2)} ${t('sharedNmi')}`; case 'km': default: - return `${(value * 0.001).toFixed(2)} ${t('sharedKm')}`; + return `${(value * 0.001).toFixed(2)} ${t('sharedKm')}`; } }; @@ -62,8 +55,8 @@ export const formatSpeed = (value, unit) => { return `${(value * 1.15078).toFixed(2)} ${t('sharedMph')}`; case 'kn': default: - return `${(value * 1).toFixed(2)} ${t('sharedKn')}`; - } + return `${(value * 1).toFixed(2)} ${t('sharedKn')}`; + } }; export const formatVolume = (value, unit) => { @@ -74,16 +67,15 @@ export const formatVolume = (value, unit) => { return `${(value / 3.785).toFixed(2)} ${t('sharedGallonAbbreviation')}`; case 'ltr': default: - return `${(value / 1).toFixed(2)} ${t('sharedLiterAbbreviation')}`; - } -} - -export const formatHours = (value) => { - return moment.duration(value).humanize(); + return `${(value / 1).toFixed(2)} ${t('sharedLiterAbbreviation')}`; + } }; +export const formatHours = (value) => moment.duration(value).humanize(); + export const formatCoordinate = (key, value, unit) => { - var hemisphere, degrees, minutes, seconds; + let hemisphere; let degrees; let minutes; let + seconds; if (key === 'latitude') { hemisphere = value >= 0 ? 'N' : 'S'; } else { @@ -95,14 +87,14 @@ export const formatCoordinate = (key, value, unit) => { value = Math.abs(value); degrees = Math.floor(value); minutes = (value - degrees) * 60; - return degrees + '° ' + minutes.toFixed(6) + '\' ' + hemisphere; + return `${degrees}° ${minutes.toFixed(6)}' ${hemisphere}`; case 'dms': value = Math.abs(value); degrees = Math.floor(value); minutes = Math.floor((value - degrees) * 60); seconds = Math.round((value - degrees - minutes / 60) * 3600); - return degrees + '° ' + minutes + '\' ' + seconds + '" ' + hemisphere; + return `${degrees}° ${minutes}' ${seconds}" ${hemisphere}`; default: - return value.toFixed(6) + '°'; - } + return `${value.toFixed(6)}°`; + } }; diff --git a/modern/src/common/localization.js b/modern/src/common/localization.js index cb1420bb..9e2123a9 100644 --- a/modern/src/common/localization.js +++ b/modern/src/common/localization.js @@ -54,60 +54,60 @@ import zh from '../../../web/l10n/zh.json'; import zh_TW from '../../../web/l10n/zh_TW.json'; const supportedLanguages = { - 'af': { data: af, name: 'Afrikaans' }, - 'ar': { data: ar, name: 'العربية' }, - 'az': { data: az, name: 'Azərbaycanca' }, - 'bg': { data: bg, name: 'Български' }, - 'bn': { data: bn, name: 'বাংলা' }, - 'cs': { data: cs, name: 'Čeština' }, - 'de': { data: de, name: 'Deutsch' }, - 'da': { data: da, name: 'Dansk' }, - 'el': { data: el, name: 'Ελληνικά' }, - 'en': { data: en, name: 'English' }, - 'es': { data: es, name: 'Español' }, - 'fa': { data: fa, name: 'فارسی' }, - 'fi': { data: fi, name: 'Suomi' }, - 'fr': { data: fr, name: 'Français' }, - 'he': { data: he, name: 'עברית' }, - 'hi': { data: hi, name: 'हिन्दी' }, - 'hr': { data: hr, name: 'Hrvatski' }, - 'hu': { data: hu, name: 'Magyar' }, - 'id': { data: id, name: 'Bahasa Indonesia' }, - 'it': { data: it, name: 'Italiano' }, - 'ja': { data: ja, name: '日本語' }, - 'ka': { data: ka, name: 'ქართული' }, - 'kk': { data: kk, name: 'Қазақша' }, - 'ko': { data: ko, name: '한국어' }, - 'km': { data: km, name: 'ភាសាខ្មែរ' }, - 'lo': { data: lo, name: 'ລາວ' }, - 'lt': { data: lt, name: 'Lietuvių' }, - 'lv': { data: lv, name: 'Latviešu' }, - 'ml': { data: ml, name: 'മലയാളം' }, - 'mn': { data: mn, name: 'Монгол хэл' }, - 'ms': { data: ms, name: 'بهاس ملايو' }, - 'nb': { data: nb, name: 'Norsk bokmål' }, - 'ne': { data: ne, name: 'नेपाली' }, - 'nl': { data: nl, name: 'Nederlands' }, - 'nn': { data: nn, name: 'Norsk nynorsk' }, - 'pl': { data: pl, name: 'Polski' }, - 'pt': { data: pt, name: 'Português' }, - 'pt_BR': { data: pt_BR, name: 'Português (Brasil)' }, - 'ro': { data: ro, name: 'Română' }, - 'ru': { data: ru, name: 'Русский' }, - 'si': { data: si, name: 'සිංහල' }, - 'sk': { data: sk, name: 'Slovenčina' }, - 'sl': { data: sl, name: 'Slovenščina' }, - 'sq': { data: sq, name: 'Shqipëria' }, - 'sr': { data: sr, name: 'Srpski' }, - 'sv': { data: sv, name: 'Svenska' }, - 'ta': { data: ta, name: 'தமிழ்' }, - 'th': { data: th, name: 'ไทย' }, - 'tr': { data: tr, name: 'Türkçe' }, - 'uk': { data: uk, name: 'Українська' }, - 'uz': { data: uz, name: 'Oʻzbekcha' }, - 'vi': { data: vi, name: 'Tiếng Việt' }, - 'zh': { data: zh, name: '中文' }, - 'zh_TW': { data: zh_TW, name: '中文 (Taiwan)' } + af: { data: af, name: 'Afrikaans' }, + ar: { data: ar, name: 'العربية' }, + az: { data: az, name: 'Azərbaycanca' }, + bg: { data: bg, name: 'Български' }, + bn: { data: bn, name: 'বাংলা' }, + cs: { data: cs, name: 'Čeština' }, + de: { data: de, name: 'Deutsch' }, + da: { data: da, name: 'Dansk' }, + el: { data: el, name: 'Ελληνικά' }, + en: { data: en, name: 'English' }, + es: { data: es, name: 'Español' }, + fa: { data: fa, name: 'فارسی' }, + fi: { data: fi, name: 'Suomi' }, + fr: { data: fr, name: 'Français' }, + he: { data: he, name: 'עברית' }, + hi: { data: hi, name: 'हिन्दी' }, + hr: { data: hr, name: 'Hrvatski' }, + hu: { data: hu, name: 'Magyar' }, + id: { data: id, name: 'Bahasa Indonesia' }, + it: { data: it, name: 'Italiano' }, + ja: { data: ja, name: '日本語' }, + ka: { data: ka, name: 'ქართული' }, + kk: { data: kk, name: 'Қазақша' }, + ko: { data: ko, name: '한국어' }, + km: { data: km, name: 'ភាសាខ្មែរ' }, + lo: { data: lo, name: 'ລາວ' }, + lt: { data: lt, name: 'Lietuvių' }, + lv: { data: lv, name: 'Latviešu' }, + ml: { data: ml, name: 'മലയാളം' }, + mn: { data: mn, name: 'Монгол хэл' }, + ms: { data: ms, name: 'بهاس ملايو' }, + nb: { data: nb, name: 'Norsk bokmål' }, + ne: { data: ne, name: 'नेपाली' }, + nl: { data: nl, name: 'Nederlands' }, + nn: { data: nn, name: 'Norsk nynorsk' }, + pl: { data: pl, name: 'Polski' }, + pt: { data: pt, name: 'Português' }, + pt_BR: { data: pt_BR, name: 'Português (Brasil)' }, + ro: { data: ro, name: 'Română' }, + ru: { data: ru, name: 'Русский' }, + si: { data: si, name: 'සිංහල' }, + sk: { data: sk, name: 'Slovenčina' }, + sl: { data: sl, name: 'Slovenščina' }, + sq: { data: sq, name: 'Shqipëria' }, + sr: { data: sr, name: 'Srpski' }, + sv: { data: sv, name: 'Svenska' }, + ta: { data: ta, name: 'தமிழ்' }, + th: { data: th, name: 'ไทย' }, + tr: { data: tr, name: 'Türkçe' }, + uk: { data: uk, name: 'Українська' }, + uz: { data: uz, name: 'Oʻzbekcha' }, + vi: { data: vi, name: 'Tiếng Việt' }, + zh: { data: zh, name: '中文' }, + zh_TW: { data: zh_TW, name: '中文 (Taiwan)' }, }; const languages = window.navigator.languages !== undefined ? window.navigator.languages.slice() : []; @@ -130,10 +130,6 @@ for (let i = 0; i < languages.length; i++) { const selectedLanguage = supportedLanguages[language]; -export const findStringKeys = (predicate) => { - return Object.keys(selectedLanguage.data).filter(predicate); -} +export const findStringKeys = (predicate) => Object.keys(selectedLanguage.data).filter(predicate); -export default key => { - return selectedLanguage.data[key]; -}; +export default (key) => selectedLanguage.data[key]; diff --git a/modern/src/common/preferences.js b/modern/src/common/preferences.js index 24fe389a..aba3c82c 100644 --- a/modern/src/common/preferences.js +++ b/modern/src/common/preferences.js @@ -1,21 +1,15 @@ import { useSelector } from 'react-redux'; -export const usePreference = (key, defaultValue) => { - return useSelector(state => { - if (state.session.server.forceSettings) { - return state.session.server[key] || state.session.user[key] || defaultValue; - } else { - return state.session.user[key] || state.session.server[key] || defaultValue; - } - }); -}; +export const usePreference = (key, defaultValue) => useSelector((state) => { + if (state.session.server.forceSettings) { + return state.session.server[key] || state.session.user[key] || defaultValue; + } + return state.session.user[key] || state.session.server[key] || defaultValue; +}); -export const useAttributePreference = (key, defaultValue) => { - return useSelector(state => { - if (state.session.server.forceSettings) { - return state.session.server.attributes[key] || state.session.user.attributes[key] || defaultValue; - } else { - return state.session.user.attributes[key] || state.session.server.attributes[key] || defaultValue; - } - }); -}; +export const useAttributePreference = (key, defaultValue) => useSelector((state) => { + if (state.session.server.forceSettings) { + return state.session.server.attributes[key] || state.session.user.attributes[key] || defaultValue; + } + return state.session.user.attributes[key] || state.session.server.attributes[key] || defaultValue; +}); diff --git a/modern/src/common/stringUtils.js b/modern/src/common/stringUtils.js index 7bd68c85..fc997fe0 100644 --- a/modern/src/common/stringUtils.js +++ b/modern/src/common/stringUtils.js @@ -1,7 +1,3 @@ -export const prefixString = (prefix, value) => { - return prefix + value.charAt(0).toUpperCase() + value.slice(1); -} +export const prefixString = (prefix, value) => prefix + value.charAt(0).toUpperCase() + value.slice(1); -export const unprefixString = (prefix, value) => { - return value.charAt(prefix.length).toLowerCase() + value.slice(prefix.length + 1); -} +export const unprefixString = (prefix, value) => value.charAt(prefix.length).toLowerCase() + value.slice(prefix.length + 1); diff --git a/modern/src/components/registration/LoginForm.js b/modern/src/components/registration/LoginForm.js index 382b4fdc..e083541c 100644 --- a/modern/src/components/registration/LoginForm.js +++ b/modern/src/components/registration/LoginForm.js @@ -1,24 +1,25 @@ import React, { useState } from 'react'; -import { Grid, useMediaQuery, makeStyles, InputLabel, Select, MenuItem, FormControl, Button, TextField, Link } from '@material-ui/core'; +import { + Grid, useMediaQuery, makeStyles, InputLabel, Select, MenuItem, FormControl, Button, TextField, Link, +} from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { sessionActions } from '../../store'; import t from '../../common/localization'; -import StartPage from './../../StartPage'; +import StartPage from '../../StartPage'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ logoContainer: { textAlign: 'center', color: theme.palette.primary.main, }, resetPassword: { cursor: 'pointer', - } + }, })); const LoginForm = () => { - const classes = useStyles(); const dispatch = useDispatch(); const history = useHistory(); @@ -27,16 +28,16 @@ const LoginForm = () => { const [failed, setFailed] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const registrationEnabled = useSelector(state => state.session.server ? state.session.server['registration'] : false); - const emailEnabled = useSelector(state => state.session.server ? state.session.server['emailEnabled'] : false); + const registrationEnabled = useSelector((state) => (state.session.server ? state.session.server.registration : false)); + const emailEnabled = useSelector((state) => (state.session.server ? state.session.server.emailEnabled : false)); const handleEmailChange = (event) => { setEmail(event.target.value); - } + }; const handlePasswordChange = (event) => { setPassword(event.target.value); - } + }; const handleLogin = async (event) => { event.preventDefault(); @@ -49,38 +50,40 @@ const LoginForm = () => { setFailed(true); setPassword(''); } - } + }; - const handleSpecialKey = e => { + const handleSpecialKey = (e) => { if (e.keyCode === 13 && email && password) { handleLogin(e); } - } + }; return ( <StartPage> - <Grid container direction='column' spacing={2}> - {useMediaQuery(theme.breakpoints.down('md')) && + <Grid container direction="column" spacing={2}> + {useMediaQuery(theme.breakpoints.down('md')) + && ( <Grid item className={classes.logoContainer}> <svg height="64" width="240"> - <use xlinkHref="/logo.svg#img"></use> + <use xlinkHref="/logo.svg#img" /> </svg> </Grid> - } + )} <Grid item> <TextField required fullWidth error={failed} label={t('userEmail')} - name='email' + name="email" value={email} - autoComplete='email' + autoComplete="email" autoFocus onChange={handleEmailChange} onKeyUp={handleSpecialKey} helperText={failed && 'Invalid username or password'} - variant='filled' /> + variant="filled" + /> </Grid> <Grid item> <TextField @@ -88,22 +91,24 @@ const LoginForm = () => { fullWidth error={failed} label={t('userPassword')} - name='password' + name="password" value={password} - type='password' - autoComplete='current-password' + type="password" + autoComplete="current-password" onChange={handlePasswordChange} onKeyUp={handleSpecialKey} - variant='filled' /> + variant="filled" + /> </Grid> <Grid item> - <Button + <Button onClick={handleLogin} onKeyUp={handleSpecialKey} - variant='contained' - color='secondary' + variant="contained" + color="secondary" disabled={!email || !password} - fullWidth> + fullWidth + > {t('loginLogin')} </Button> </Grid> @@ -122,14 +127,16 @@ const LoginForm = () => { </FormControl> </Grid> </Grid> - {emailEnabled && <Grid item container justify="flex-end"> + {emailEnabled && ( + <Grid item container justify="flex-end"> <Grid item> - <Link onClick={() => history.push('/reset-password')} className={classes.resetPassword} underline="none">{t('loginReset')}</Link> + <Link onClick={() => history.push('/reset-password')} className={classes.resetPassword} underline="none">{t('loginReset')}</Link> </Grid> - </Grid>} + </Grid> + )} </Grid> </StartPage> - ) -} + ); +}; export default LoginForm; diff --git a/modern/src/components/registration/RegisterForm.js b/modern/src/components/registration/RegisterForm.js index b2a8222a..06f53551 100644 --- a/modern/src/components/registration/RegisterForm.js +++ b/modern/src/components/registration/RegisterForm.js @@ -1,27 +1,28 @@ import React, { useState } from 'react'; -import { Grid, Button, TextField, Typography, Link, makeStyles, Snackbar } from '@material-ui/core'; +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'; -import StartPage from './../../StartPage'; -import t from './../../common/localization'; +import StartPage from '../../StartPage'; +import t from '../../common/localization'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ register: { fontSize: theme.spacing(3), fontWeight: 500, marginLeft: theme.spacing(2), - textTransform: "uppercase" + textTransform: 'uppercase', }, link: { fontSize: theme.spacing(3), fontWeight: 500, marginTop: theme.spacing(0.5), - cursor: 'pointer' - } + cursor: 'pointer', + }, })); const RegisterForm = () => { - const classes = useStyles(); const history = useHistory(); @@ -30,21 +31,19 @@ const RegisterForm = () => { const [password, setPassword] = useState(''); const [snackbarOpen, setSnackbarOpen] = useState(false); - const submitDisabled = () => { - return !name || !/(.+)@(.+)\.(.{2,})/.test(email) || !password; - } + const submitDisabled = () => !name || !/(.+)@(.+)\.(.{2,})/.test(email) || !password; const handleRegister = async () => { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({name, email, password}) + body: JSON.stringify({ name, email, password }), }); if (response.ok) { setSnackbarOpen(true); - } - } + } + }; return ( <StartPage> @@ -53,18 +52,19 @@ const RegisterForm = () => { open={snackbarOpen} onClose={() => history.push('/login')} autoHideDuration={6000} - message={t('loginCreated')} /> - <Grid container direction='column' spacing={2}> + message={t('loginCreated')} + /> + <Grid container direction="column" spacing={2}> <Grid container item> <Grid item> - <Typography className={classes.link} color='primary'> + <Typography className={classes.link} color="primary"> <Link onClick={() => history.push('/login')}> <ArrowBackIcon /> </Link> </Typography> </Grid> <Grid item xs> - <Typography className={classes.register} color='primary'> + <Typography className={classes.register} color="primary"> {t('loginRegister')} </Typography> </Grid> @@ -74,50 +74,54 @@ const RegisterForm = () => { required fullWidth label={t('sharedName')} - name='name' + name="name" value={name || ''} - autoComplete='name' + autoComplete="name" autoFocus - onChange={event => setName(event.target.value)} - variant='filled' /> + onChange={(event) => setName(event.target.value)} + variant="filled" + /> </Grid> <Grid item> <TextField required fullWidth - type='email' + type="email" label={t('userEmail')} - name='email' + name="email" value={email || ''} - autoComplete='email' - onChange={event => setEmail(event.target.value)} - variant='filled' /> + autoComplete="email" + onChange={(event) => setEmail(event.target.value)} + variant="filled" + /> </Grid> <Grid item> <TextField required fullWidth label={t('userPassword')} - name='password' + name="password" value={password || ''} - type='password' - autoComplete='current-password' - onChange={event => setPassword(event.target.value)} - variant='filled' /> + type="password" + autoComplete="current-password" + onChange={(event) => setPassword(event.target.value)} + variant="filled" + /> </Grid> <Grid item> <Button - variant='contained' - color="secondary" - onClick={handleRegister} + variant="contained" + color="secondary" + onClick={handleRegister} disabled={submitDisabled()} - fullWidth> - {t('loginRegister')} + fullWidth + > + {t('loginRegister')} </Button> </Grid> </Grid> </StartPage> - ) -} + ); +}; export default RegisterForm; diff --git a/modern/src/components/registration/ResetPasswordForm.js b/modern/src/components/registration/ResetPasswordForm.js index c268f808..f4dd2e4d 100644 --- a/modern/src/components/registration/ResetPasswordForm.js +++ b/modern/src/components/registration/ResetPasswordForm.js @@ -1,12 +1,9 @@ import React from 'react'; -const ResetPasswordForm = () => { - - return ( - <> - <div>Reset Password Comming Soon!!!</div> - </> - ) -} +const ResetPasswordForm = () => ( + <> + <div>Reset Password Comming Soon!!!</div> + </> +); export default ResetPasswordForm; diff --git a/modern/src/components/reports/ReportNavbar.js b/modern/src/components/reports/ReportNavbar.js index ac01fad9..16807e89 100644 --- a/modern/src/components/reports/ReportNavbar.js +++ b/modern/src/components/reports/ReportNavbar.js @@ -1,26 +1,28 @@ import React from 'react'; -import { AppBar, Toolbar, Typography, IconButton } from '@material-ui/core'; +import { + AppBar, Toolbar, Typography, IconButton, +} from '@material-ui/core'; import MenuIcon from '@material-ui/icons/Menu'; import t from '../../common/localization'; -const ReportNavbar = ({ setOpenDrawer, reportTitle }) => { - - return ( - <AppBar position="fixed" color="inherit"> - <Toolbar> - <IconButton - color="inherit" - aria-label="open drawer" - edge="start" - onClick={() => setOpenDrawer(true)}> - <MenuIcon /> - </IconButton> - <Typography variant="h6" noWrap> - {t('reportTitle')} {` / ${reportTitle}`} - </Typography> - </Toolbar> - </AppBar> - ) -} +const ReportNavbar = ({ setOpenDrawer, reportTitle }) => ( + <AppBar position="fixed" color="inherit"> + <Toolbar> + <IconButton + color="inherit" + aria-label="open drawer" + edge="start" + onClick={() => setOpenDrawer(true)} + > + <MenuIcon /> + </IconButton> + <Typography variant="h6" noWrap> + {t('reportTitle')} + {' '} + {` / ${reportTitle}`} + </Typography> + </Toolbar> + </AppBar> +); export default ReportNavbar; diff --git a/modern/src/components/reports/ReportSidebar.js b/modern/src/components/reports/ReportSidebar.js index 90e20c05..686fc040 100644 --- a/modern/src/components/reports/ReportSidebar.js +++ b/modern/src/components/reports/ReportSidebar.js @@ -1,21 +1,23 @@ import React from 'react'; -import { List, ListItem, ListItemText, ListItemIcon } from '@material-ui/core'; +import { + List, ListItem, ListItemText, ListItemIcon, +} from '@material-ui/core'; import { Link, useLocation } from 'react-router-dom'; const ReportSidebar = ({ routes }) => { - const location = useLocation(); return ( - <List disablePadding style={{paddingTop: '16px'}}> - {routes.map((route, index) => ( + <List disablePadding style={{ paddingTop: '16px' }}> + {routes.map((route) => ( <ListItem disableRipple component={Link} - key={`${route}${index}`} + key={route} button to={route.href} - selected={route.href === location.pathname}> + selected={route.href === location.pathname} + > <ListItemIcon> {route.icon} </ListItemIcon> @@ -23,7 +25,7 @@ const ReportSidebar = ({ routes }) => { </ListItem> ))} </List> - ) -} + ); +}; export default ReportSidebar; diff --git a/modern/src/form/LinkField.js b/modern/src/form/LinkField.js index b228fb34..81467a1b 100644 --- a/modern/src/form/LinkField.js +++ b/modern/src/form/LinkField.js @@ -1,4 +1,6 @@ -import { FormControl, InputLabel, MenuItem, Select } from '@material-ui/core'; +import { + FormControl, InputLabel, MenuItem, Select, +} from '@material-ui/core'; import React, { useState } from 'react'; import { useEffectAsync } from '../reactHelper'; @@ -11,8 +13,8 @@ const LinkField = ({ baseId, keyBase, keyLink, - keyGetter = item => item.id, - titleGetter = item => item.name, + keyGetter = (item) => item.id, + titleGetter = (item) => item.name, }) => { const [items, setItems] = useState(); const [linked, setLinked] = useState(); @@ -28,34 +30,36 @@ const LinkField = ({ const response = await fetch(endpointLinked); if (response.ok) { const data = await response.json(); - setLinked(data.map(it => it.id)); + setLinked(data.map((it) => it.id)); } }, []); - const createBody = linkId => { + const createBody = (linkId) => { const body = {}; body[keyBase] = baseId; body[keyLink] = linkId; return body; - } + }; - const onChange = async event => { + const onChange = async (event) => { const oldValue = linked; const newValue = event.target.value; - for (const added of newValue.filter(it => !oldValue.includes(it))) { - await fetch('/api/permissions', { + const results = []; + newValue.filter((it) => !oldValue.includes(it)).forEach((added) => { + results.push(fetch('/api/permissions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(createBody(added)), - }); - } - for (const removed of oldValue.filter(it => !newValue.includes(it))) { - await fetch('/api/permissions', { + })); + }); + oldValue.filter((it) => !newValue.includes(it)).forEach((removed) => { + results.push(fetch('/api/permissions', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(createBody(removed)), - }); - } + })); + }); + await Promise.all(results); setLinked(newValue); }; @@ -66,16 +70,16 @@ const LinkField = ({ <Select multiple value={linked} - onChange={onChange}> - {items.map(item => ( + onChange={onChange} + > + {items.map((item) => ( <MenuItem key={keyGetter(item)} value={keyGetter(item)}>{titleGetter(item)}</MenuItem> ))} </Select> </FormControl> ); - } else { - return null; } -} + return null; +}; export default LinkField; diff --git a/modern/src/form/SelectField.js b/modern/src/form/SelectField.js index b179c58e..303d203c 100644 --- a/modern/src/form/SelectField.js +++ b/modern/src/form/SelectField.js @@ -1,4 +1,6 @@ -import { FormControl, InputLabel, MenuItem, Select } from '@material-ui/core'; +import { + FormControl, InputLabel, MenuItem, Select, +} from '@material-ui/core'; import React, { useState } from 'react'; import { useEffectAsync } from '../reactHelper'; @@ -12,8 +14,8 @@ const SelectField = ({ onChange, endpoint, data, - keyGetter = item => item.id, - titleGetter = item => item.name, + keyGetter = (item) => item.id, + titleGetter = (item) => item.name, }) => { const [items, setItems] = useState(data); @@ -33,19 +35,18 @@ const SelectField = ({ <Select multiple={multiple} value={value} - onChange={onChange}> - {!multiple && emptyValue !== null && - <MenuItem value={emptyValue}> </MenuItem> - } - {items.map(item => ( + onChange={onChange} + > + {!multiple && emptyValue !== null + && <MenuItem value={emptyValue}> </MenuItem>} + {items.map((item) => ( <MenuItem key={keyGetter(item)} value={keyGetter(item)}>{titleGetter(item)}</MenuItem> ))} </Select> </FormControl> ); - } else { - return null; } -} + return null; +}; export default SelectField; diff --git a/modern/src/index.js b/modern/src/index.js index 82ddef95..155bf623 100644 --- a/modern/src/index.js +++ b/modern/src/index.js @@ -1,7 +1,7 @@ -import 'typeface-roboto' +import 'typeface-roboto'; import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter } from 'react-router-dom' +import { HashRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import App from './App'; diff --git a/modern/src/map/AccuracyMap.js b/modern/src/map/AccuracyMap.js index e81fc8fb..4baa1054 100644 --- a/modern/src/map/AccuracyMap.js +++ b/modern/src/map/AccuracyMap.js @@ -7,33 +7,31 @@ import { map } from './Map'; const AccuracyMap = () => { const id = 'accuracy'; - const positions = useSelector(state => ({ + const positions = useSelector((state) => ({ type: 'FeatureCollection', - features: Object.values(state.positions.items).filter(position => position.accuracy > 0).map(position => - circle([position.longitude, position.latitude], position.accuracy * 0.001) - ), + features: Object.values(state.positions.items).filter((position) => position.accuracy > 0).map((position) => circle([position.longitude, position.latitude], position.accuracy * 0.001)), })); useEffect(() => { map.addSource(id, { - 'type': 'geojson', - 'data': { + type: 'geojson', + data: { type: 'FeatureCollection', - features: [] - } + features: [], + }, }); map.addLayer({ - 'source': id, - 'id': id, - 'type': 'fill', - 'filter': [ - 'all', - ['==', '$type', 'Polygon'], + source: id, + id, + type: 'fill', + filter: [ + 'all', + ['==', '$type', 'Polygon'], ], - 'paint': { - 'fill-color':'#3bb2d0', - 'fill-outline-color':'#3bb2d0', - 'fill-opacity':0.25, + paint: { + 'fill-color': '#3bb2d0', + 'fill-outline-color': '#3bb2d0', + 'fill-opacity': 0.25, }, }); @@ -48,6 +46,6 @@ const AccuracyMap = () => { }, [positions]); return null; -} +}; export default AccuracyMap; diff --git a/modern/src/map/CurrentLocationMap.js b/modern/src/map/CurrentLocationMap.js index 06aaad0f..69724ce1 100644 --- a/modern/src/map/CurrentLocationMap.js +++ b/modern/src/map/CurrentLocationMap.js @@ -16,6 +16,6 @@ const CurrentLocationMap = () => { }, []); return null; -} +}; export default CurrentLocationMap; diff --git a/modern/src/map/CurrentPositionsMap.js b/modern/src/map/CurrentPositionsMap.js index 0bfe4fcd..9e00774a 100644 --- a/modern/src/map/CurrentPositionsMap.js +++ b/modern/src/map/CurrentPositionsMap.js @@ -1,11 +1,11 @@ -import React, { } from 'react'; +import React, { } from 'react'; import { useSelector } from 'react-redux'; import PositionsMap from './PositionsMap'; const CurrentPositionsMap = () => { - const positions = useSelector(state => Object.values(state.positions.items)); + const positions = useSelector((state) => Object.values(state.positions.items)); return (<PositionsMap positions={positions} />); -} +}; export default CurrentPositionsMap; diff --git a/modern/src/map/GeofenceEditMap.js b/modern/src/map/GeofenceEditMap.js index fe843382..d639c192 100644 --- a/modern/src/map/GeofenceEditMap.js +++ b/modern/src/map/GeofenceEditMap.js @@ -1,13 +1,13 @@ -import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css' +import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; import MapboxDraw from '@mapbox/mapbox-gl-draw'; 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 { map } from './Map'; import { geofenceToFeature, geometryToArea } from './mapUtil'; -import { useDispatch, useSelector } from 'react-redux'; import { geofencesActions } from '../store'; -import { useHistory } from 'react-router-dom'; const draw = new MapboxDraw({ displayControlsDefault: false, @@ -17,15 +17,15 @@ const draw = new MapboxDraw({ }, userProperties: true, styles: [...theme, { - 'id': 'gl-draw-title', - 'type': 'symbol', - 'filter': ['all'], - 'layout': { + id: 'gl-draw-title', + type: 'symbol', + filter: ['all'], + layout: { 'text-field': '{user_name}', 'text-font': ['Roboto Regular'], 'text-size': 12, }, - 'paint': { + paint: { 'text-halo-color': 'white', 'text-halo-width': 1, }, @@ -36,25 +36,25 @@ const GeofenceEditMap = () => { const dispatch = useDispatch(); const history = useHistory(); - const geofences = useSelector(state => Object.values(state.geofences.items)); + const geofences = useSelector((state) => Object.values(state.geofences.items)); const refreshGeofences = async () => { const response = await fetch('/api/geofences'); if (response.ok) { dispatch(geofencesActions.refresh(await response.json())); } - } + }; useEffect(() => { refreshGeofences(); map.addControl(draw, 'top-left'); - map.on('draw.create', async event => { + map.on('draw.create', async (event) => { const feature = event.features[0]; const newItem = { name: '', area: geometryToArea(feature.geometry) }; draw.delete(feature.id); - const response = await fetch(`/api/geofences`, { + const response = await fetch('/api/geofences', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newItem), @@ -65,7 +65,7 @@ const GeofenceEditMap = () => { } }); - map.on('draw.delete', async event => { + map.on('draw.delete', async (event) => { const feature = event.features[0]; const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' }); if (response.ok) { @@ -73,9 +73,9 @@ const GeofenceEditMap = () => { } }); - map.on('draw.update', async event => { + map.on('draw.update', async (event) => { const feature = event.features[0]; - const item = geofences.find(i => i.id === feature.id); + const item = geofences.find((i) => i.id === feature.id); if (item) { const updatedItem = { ...item, area: geometryToArea(feature.geometry) }; const response = await fetch(`/api/geofences/${feature.id}`, { @@ -94,12 +94,12 @@ const GeofenceEditMap = () => { useEffect(() => { draw.deleteAll(); - for (const geofence of geofences) { + geofences.forEach((geofence) => { draw.add(geofenceToFeature(geofence)); - } + }); }, [geofences]); return null; -} +}; export default GeofenceEditMap; diff --git a/modern/src/map/GeofenceMap.js b/modern/src/map/GeofenceMap.js index 8db175a2..d00cbb18 100644 --- a/modern/src/map/GeofenceMap.js +++ b/modern/src/map/GeofenceMap.js @@ -7,49 +7,49 @@ import { geofenceToFeature } from './mapUtil'; const GeofenceMap = () => { const id = 'geofences'; - const geofences = useSelector(state => Object.values(state.geofences.items)); + const geofences = useSelector((state) => Object.values(state.geofences.items)); useEffect(() => { map.addSource(id, { - 'type': 'geojson', - 'data': { + type: 'geojson', + data: { type: 'FeatureCollection', - features: [] - } + features: [], + }, }); map.addLayer({ - 'source': id, - 'id': 'geofences-fill', - 'type': 'fill', - 'filter': [ - 'all', - ['==', '$type', 'Polygon'], + source: id, + id: 'geofences-fill', + type: 'fill', + filter: [ + 'all', + ['==', '$type', 'Polygon'], ], - 'paint': { - 'fill-color':'#3bb2d0', - 'fill-outline-color':'#3bb2d0', - 'fill-opacity':0.1, + paint: { + 'fill-color': '#3bb2d0', + 'fill-outline-color': '#3bb2d0', + 'fill-opacity': 0.1, }, }); map.addLayer({ - 'source': id, - 'id': 'geofences-line', - 'type': 'line', - 'paint': { - 'line-color': '#3bb2d0', - 'line-width': 2, + source: id, + id: 'geofences-line', + type: 'line', + paint: { + 'line-color': '#3bb2d0', + 'line-width': 2, }, }); map.addLayer({ - 'source': id, - 'id': 'geofences-title', - 'type': 'symbol', - 'layout': { + source: id, + id: 'geofences-title', + type: 'symbol', + layout: { 'text-field': '{name}', 'text-font': ['Roboto Regular'], 'text-size': 12, }, - 'paint': { + paint: { 'text-halo-color': 'white', 'text-halo-width': 1, }, @@ -66,11 +66,11 @@ const GeofenceMap = () => { useEffect(() => { map.getSource(id).setData({ type: 'FeatureCollection', - features: geofences.map(geofenceToFeature) + features: geofences.map(geofenceToFeature), }); }, [geofences]); return null; -} +}; export default GeofenceMap; diff --git a/modern/src/map/Map.js b/modern/src/map/Map.js index 46c59d2d..7dd1c2a8 100644 --- a/modern/src/map/Map.js +++ b/modern/src/map/Map.js @@ -1,9 +1,11 @@ import 'maplibre-gl/dist/maplibre-gl.css'; import './switcher/switcher.css'; import maplibregl from 'maplibre-gl'; +import React, { + useRef, useLayoutEffect, useEffect, useState, +} from 'react'; import { SwitcherControl } from './switcher/switcher'; -import React, { useRef, useLayoutEffect, useEffect, useState } from 'react'; -import { deviceCategories } from '../common/deviceCategories'; +import deviceCategories from '../common/deviceCategories'; import { prepareIcon, loadImage } from './mapUtil'; import { styleCarto, styleMapbox, styleOsm } from './mapStyles'; import t from '../common/localization'; @@ -22,18 +24,18 @@ export const map = new maplibregl.Map({ let ready = false; const readyListeners = new Set(); -const addReadyListener = listener => { +const addReadyListener = (listener) => { readyListeners.add(listener); listener(ready); }; -const removeReadyListener = listener => { +const removeReadyListener = (listener) => { readyListeners.delete(listener); }; -const updateReadyValue = value => { +const updateReadyValue = (value) => { ready = value; - readyListeners.forEach(listener => listener(value)); + readyListeners.forEach((listener) => listener(value)); }; const initMap = async () => { @@ -42,13 +44,16 @@ const initMap = async () => { map.addImage('background', await prepareIcon(background), { pixelRatio: window.devicePixelRatio, }); - await Promise.all(deviceCategories.map(async category => { - for (const color of ['green', 'red', 'gray']) { - const icon = await loadImage(`images/icon/${category}.svg`); - map.addImage(`${category}-${color}`, prepareIcon(background, icon, palette.common[color]), { - pixelRatio: window.devicePixelRatio, - }); - } + await Promise.all(deviceCategories.map(async (category) => { + const results = []; + ['green', 'red', 'gray'].forEach((color) => { + results.push(loadImage(`images/icon/${category}.svg`).then((icon) => { + map.addImage(`${category}-${color}`, prepareIcon(background, icon, palette.common[color]), { + pixelRatio: window.devicePixelRatio, + }); + })); + }); + await Promise.all(results); })); updateReadyValue(true); }; @@ -93,7 +98,7 @@ const Map = ({ children }) => { }, [mapboxAccessToken]); useEffect(() => { - const listener = ready => setMapReady(ready); + const listener = (ready) => setMapReady(ready); addReadyListener(listener); return () => { removeReadyListener(listener); diff --git a/modern/src/map/PositionsMap.js b/modern/src/map/PositionsMap.js index 35d6d926..2de01d2c 100644 --- a/modern/src/map/PositionsMap.js +++ b/modern/src/map/PositionsMap.js @@ -3,9 +3,9 @@ import ReactDOM from 'react-dom'; import maplibregl from 'maplibre-gl'; import { Provider, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { map } from './Map'; import store from '../store'; -import { useHistory } from 'react-router-dom'; import StatusView from './StatusView'; const PositionsMap = ({ positions }) => { @@ -13,9 +13,9 @@ const PositionsMap = ({ positions }) => { const clusters = `${id}-clusters`; const history = useHistory(); - const devices = useSelector(state => state.devices.items); + const devices = useSelector((state) => state.devices.items); - const deviceColor = device => { + const deviceColor = (device) => { switch (device.status) { case 'online': return 'green'; @@ -33,15 +33,15 @@ const PositionsMap = ({ positions }) => { name: device.name, category: device.category || 'default', color: deviceColor(device), - } + }; }; const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer'; const onMouseLeave = () => map.getCanvas().style.cursor = ''; - const onMarkerClick = useCallback(event => { + const onMarkerClick = useCallback((event) => { const feature = event.features[0]; - let coordinates = feature.geometry.coordinates.slice(); + const coordinates = feature.geometry.coordinates.slice(); while (Math.abs(event.lngLat.lng - coordinates[0]) > 180) { coordinates[0] += event.lngLat.lng > coordinates[0] ? 360 : -360; } @@ -49,50 +49,52 @@ const PositionsMap = ({ positions }) => { const placeholder = document.createElement('div'); ReactDOM.render( <Provider store={store}> - <StatusView deviceId={feature.properties.deviceId} onShowDetails={positionId => history.push(`/position/${positionId}`)} /> + <StatusView deviceId={feature.properties.deviceId} onShowDetails={(positionId) => history.push(`/position/${positionId}`)} /> </Provider>, - placeholder + placeholder, ); new maplibregl.Popup({ offset: 25, - anchor: 'top' + anchor: 'top', }) .setDOMContent(placeholder) .setLngLat(coordinates) .addTo(map); }, [history]); - const onClusterClick = event => { + const onClusterClick = (event) => { const features = map.queryRenderedFeatures(event.point, { layers: [clusters], }); const clusterId = features[0].properties.cluster_id; map.getSource(id).getClusterExpansionZoom(clusterId, (error, zoom) => { - if (!error) map.easeTo({ - center: features[0].geometry.coordinates, - zoom: zoom, - }); + if (!error) { + map.easeTo({ + center: features[0].geometry.coordinates, + zoom, + }); + } }); }; useEffect(() => { map.addSource(id, { - 'type': 'geojson', - 'data': { + type: 'geojson', + data: { type: 'FeatureCollection', features: [], }, - 'cluster': true, - 'clusterMaxZoom': 14, - 'clusterRadius': 50, + cluster: true, + clusterMaxZoom: 14, + clusterRadius: 50, }); map.addLayer({ - 'id': id, - 'type': 'symbol', - 'source': id, - 'filter': ['!', ['has', 'point_count']], - 'layout': { + id, + type: 'symbol', + source: id, + filter: ['!', ['has', 'point_count']], + layout: { 'icon-image': '{category}-{color}', 'icon-allow-overlap': true, 'text-field': '{name}', @@ -102,17 +104,17 @@ const PositionsMap = ({ positions }) => { 'text-font': ['Roboto Regular'], 'text-size': 12, }, - 'paint': { + paint: { 'text-halo-color': 'white', 'text-halo-width': 1, }, }); map.addLayer({ - 'id': clusters, - 'type': 'symbol', - 'source': id, - 'filter': ['has', 'point_count'], - 'layout': { + id: clusters, + type: 'symbol', + source: id, + filter: ['has', 'point_count'], + layout: { 'icon-image': 'background', 'text-field': '{point_count_abbreviated}', 'text-font': ['Roboto Regular'], @@ -120,7 +122,6 @@ const PositionsMap = ({ positions }) => { }, }); - map.on('mouseenter', id, onMouseEnter); map.on('mouseleave', id, onMouseLeave); map.on('mouseenter', clusters, onMouseEnter); @@ -129,7 +130,7 @@ const PositionsMap = ({ positions }) => { map.on('click', clusters, onClusterClick); return () => { - Array.from(map.getContainer().getElementsByClassName('maplibregl-popup')).forEach(el => el.remove()); + Array.from(map.getContainer().getElementsByClassName('maplibregl-popup')).forEach((el) => el.remove()); map.off('mouseenter', id, onMouseEnter); map.off('mouseleave', id, onMouseLeave); @@ -147,18 +148,18 @@ const PositionsMap = ({ positions }) => { useEffect(() => { map.getSource(id).setData({ type: 'FeatureCollection', - features: positions.filter(it => devices.hasOwnProperty(it.deviceId)).map(position => ({ + features: positions.filter((it) => devices.hasOwnProperty(it.deviceId)).map((position) => ({ type: 'Feature', geometry: { type: 'Point', coordinates: [position.longitude, position.latitude], }, properties: createFeature(devices, position), - })) + })), }); }, [devices, positions]); return null; -} +}; export default PositionsMap; diff --git a/modern/src/map/ReplayPathMap.js b/modern/src/map/ReplayPathMap.js index b40aa690..62b3f279 100644 --- a/modern/src/map/ReplayPathMap.js +++ b/modern/src/map/ReplayPathMap.js @@ -7,8 +7,8 @@ const ReplayPathMap = ({ positions }) => { useEffect(() => { map.addSource(id, { - 'type': 'geojson', - 'data': { + type: 'geojson', + data: { type: 'Feature', geometry: { type: 'LineString', @@ -17,16 +17,16 @@ const ReplayPathMap = ({ positions }) => { }, }); map.addLayer({ - 'source': id, - 'id': id, - 'type': 'line', - 'layout': { + source: id, + id, + type: 'line', + layout: { 'line-join': 'round', 'line-cap': 'round', }, - 'paint': { - 'line-color': '#333', - 'line-width': 5, + paint: { + 'line-color': '#333', + 'line-width': 5, }, }); @@ -37,23 +37,25 @@ const ReplayPathMap = ({ positions }) => { }, []); useEffect(() => { - const coordinates = positions.map(item => [item.longitude, item.latitude]); + const coordinates = positions.map((item) => [item.longitude, item.latitude]); map.getSource(id).setData({ type: 'Feature', geometry: { type: 'LineString', - coordinates: coordinates, + coordinates, }, }); if (coordinates.length) { const bounds = coordinates.reduce((bounds, item) => bounds.extend(item), new maplibregl.LngLatBounds(coordinates[0], coordinates[0])); map.fitBounds(bounds, { - padding: { top: 50, bottom: 250, left: 25, right: 25 }, + padding: { + top: 50, bottom: 250, left: 25, right: 25, + }, }); } }, [positions]); return null; -} +}; export default ReplayPathMap; diff --git a/modern/src/map/SelectedDeviceMap.js b/modern/src/map/SelectedDeviceMap.js index 655fca98..e6c5f58f 100644 --- a/modern/src/map/SelectedDeviceMap.js +++ b/modern/src/map/SelectedDeviceMap.js @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux'; import { map } from './Map'; const SelectedDeviceMap = () => { - const mapCenter = useSelector(state => { + const mapCenter = useSelector((state) => { if (state.devices.selectedId) { const position = state.positions.items[state.devices.selectedId] || null; if (position) { @@ -19,6 +19,6 @@ const SelectedDeviceMap = () => { }, [mapCenter]); return null; -} +}; export default SelectedDeviceMap; diff --git a/modern/src/map/StatusView.js b/modern/src/map/StatusView.js index ae049af1..20e5b749 100644 --- a/modern/src/map/StatusView.js +++ b/modern/src/map/StatusView.js @@ -1,27 +1,68 @@ -import t from '../common/localization' import React from 'react'; import { useSelector } from 'react-redux'; +import t from '../common/localization'; import { formatPosition } from '../common/formatter'; const StatusView = ({ deviceId, onShowDetails }) => { - const device = useSelector(state => state.devices.items[deviceId]); - const position = useSelector(state => state.positions.items[deviceId]); + const device = useSelector((state) => state.devices.items[deviceId]); + const position = useSelector((state) => state.positions.items[deviceId]); - const handleClick = e => { + const handleClick = (e) => { e.preventDefault(); onShowDetails(position.id); }; return ( <> - <b>{t('deviceStatus')}:</b> {formatPosition(device.status, 'status')}<br /> - <b>{t('sharedLocation')}:</b> {formatPosition(position, 'latitude')} {formatPosition(position, 'longitude')}<br /> - <b>{t('positionSpeed')}:</b> {formatPosition(position.speed, 'speed')}<br /> - <b>{t('positionCourse')}:</b> {formatPosition(position.course, 'course')}<br /> - <b>{t('positionDistance')}:</b> {formatPosition(position.attributes.totalDistance, 'distance')}<br /> - {position.attributes.batteryLevel && - <><b>{t('positionBattery')}:</b> {formatPosition(position.attributes.batteryLevel, 'batteryLevel')}<br /></> - } + <b> + {t('deviceStatus')} + : + </b> + {' '} + {formatPosition(device.status, 'status')} + <br /> + <b> + {t('sharedLocation')} + : + </b> + {' '} + {formatPosition(position, 'latitude')} + {' '} + {formatPosition(position, 'longitude')} + <br /> + <b> + {t('positionSpeed')} + : + </b> + {' '} + {formatPosition(position.speed, 'speed')} + <br /> + <b> + {t('positionCourse')} + : + </b> + {' '} + {formatPosition(position.course, 'course')} + <br /> + <b> + {t('positionDistance')} + : + </b> + {' '} + {formatPosition(position.attributes.totalDistance, 'distance')} + <br /> + {position.attributes.batteryLevel + && ( + <> + <b> + {t('positionBattery')} + : + </b> + {' '} + {formatPosition(position.attributes.batteryLevel, 'batteryLevel')} + <br /> + </> + )} <a href="/" onClick={handleClick}>{t('sharedShowDetails')}</a> </> ); diff --git a/modern/src/map/mapStyles.js b/modern/src/map/mapStyles.js index 00a8666d..9650ead5 100644 --- a/modern/src/map/mapStyles.js +++ b/modern/src/map/mapStyles.js @@ -4,7 +4,7 @@ export const styleCustom = (url, attribution) => ({ osm: { type: 'raster', tiles: [url], - attribution: attribution, + attribution, tileSize: 256, }, }, @@ -22,30 +22,30 @@ export const styleOsm = () => styleCustom( ); export const styleCarto = () => ({ - 'version': 8, - 'sources': { + version: 8, + sources: { 'raster-tiles': { - 'type': 'raster', - 'tiles': [ - 'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', - 'https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', - 'https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', - 'https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png' + type: 'raster', + tiles: [ + 'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', + 'https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', + 'https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', + 'https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png', ], - 'tileSize': 256, - 'attribution': '© <a target="_top" rel="noopener" href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a target="_top" rel="noopener" href="https://carto.com/attribution">CARTO</a>' - } + tileSize: 256, + attribution: '© <a target="_top" rel="noopener" href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a target="_top" rel="noopener" href="https://carto.com/attribution">CARTO</a>', + }, }, - 'glyphs': 'https://cdn.traccar.com/map/fonts/{fontstack}/{range}.pbf', - 'layers': [ + glyphs: 'https://cdn.traccar.com/map/fonts/{fontstack}/{range}.pbf', + layers: [ { - 'id': 'simple-tiles', - 'type': 'raster', - 'source': 'raster-tiles', - 'minzoom': 0, - 'maxzoom': 22, - } - ] + id: 'simple-tiles', + type: 'raster', + source: 'raster-tiles', + minzoom: 0, + maxzoom: 22, + }, + ], }); export const styleMapbox = (style) => `mapbox://styles/mapbox/${style}`; diff --git a/modern/src/map/mapUtil.js b/modern/src/map/mapUtil.js index 67ce345e..e3c32f46 100644 --- a/modern/src/map/mapUtil.js +++ b/modern/src/map/mapUtil.js @@ -2,13 +2,11 @@ import { parse, stringify } from 'wellknown'; import canvasTintImage from 'canvas-tint-image'; import circle from '@turf/circle'; -export const loadImage = (url) => { - return new Promise(imageLoaded => { - const image = new Image(); - image.onload = () => imageLoaded(image); - image.src = url; - }); -}; +export const loadImage = (url) => new Promise((imageLoaded) => { + const image = new Image(); + image.onload = () => imageLoaded(image); + image.src = url; +}); export const prepareIcon = (background, icon, color) => { const pixelRatio = window.devicePixelRatio; @@ -32,44 +30,39 @@ export const prepareIcon = (background, icon, color) => { return context.getImageData(0, 0, canvas.width, canvas.height); }; -export const reverseCoordinates = it => { +export const reverseCoordinates = (it) => { if (!it) { return it; - } else if (Array.isArray(it)) { + } if (Array.isArray(it)) { if (it.length === 2 && !Number.isNaN(it[0]) && !Number.isNaN(it[1])) { return [it[1], it[0]]; - } else { - return it.map(it => reverseCoordinates(it)); - } - } else { - return { - ...it, - coordinates: reverseCoordinates(it.coordinates), } + return it.map((it) => reverseCoordinates(it)); } -} + return { + ...it, + coordinates: reverseCoordinates(it.coordinates), + }; +}; export const geofenceToFeature = (item) => { if (item.area.indexOf('CIRCLE') > -1) { - let coordinates = item.area.replace(/CIRCLE|\(|\)|,/g, " ").trim().split(/ +/); - var options = { steps: 32, units: 'meters' }; - let polygon = circle([Number(coordinates[1]), Number(coordinates[0])], Number(coordinates[2]), options); + const coordinates = item.area.replace(/CIRCLE|\(|\)|,/g, ' ').trim().split(/ +/); + const options = { steps: 32, units: 'meters' }; + const polygon = circle([Number(coordinates[1]), Number(coordinates[0])], Number(coordinates[2]), options); return { id: item.id, type: 'Feature', geometry: polygon.geometry, - properties: { name: item.name } - }; - } else { - return { - id: item.id, - type: 'Feature', - geometry: reverseCoordinates(parse(item.area)), - properties: { name: item.name } + properties: { name: item.name }, }; } -} + return { + id: item.id, + type: 'Feature', + geometry: reverseCoordinates(parse(item.area)), + properties: { name: item.name }, + }; +}; -export const geometryToArea = (geometry) => { - return stringify(reverseCoordinates(geometry)); -} +export const geometryToArea = (geometry) => stringify(reverseCoordinates(geometry)); diff --git a/modern/src/map/switcher/switcher.js b/modern/src/map/switcher/switcher.js index a755645b..e9076aa6 100644 --- a/modern/src/map/switcher/switcher.js +++ b/modern/src/map/switcher/switcher.js @@ -1,5 +1,4 @@ export class SwitcherControl { - constructor(styles, defaultStyle, beforeSwitch, afterSwitch) { this.styles = styles; this.defaultStyle = defaultStyle; @@ -27,8 +26,8 @@ export class SwitcherControl { styleElement.innerText = style.title; styleElement.classList.add(style.title.replace(/[^a-z0-9-]/gi, '_')); styleElement.dataset.uri = JSON.stringify(style.uri); - styleElement.addEventListener('click', event => { - const srcElement = event.srcElement; + styleElement.addEventListener('click', (event) => { + const { srcElement } = event; if (srcElement.classList.contains('active')) { return; } diff --git a/modern/src/reactHelper.js b/modern/src/reactHelper.js index b0eb0169..f3ef78dd 100644 --- a/modern/src/reactHelper.js +++ b/modern/src/reactHelper.js @@ -1,7 +1,6 @@ - import { useRef, useEffect } from 'react'; -export const usePrevious = value => { +export const usePrevious = (value) => { const ref = useRef(); useEffect(() => { ref.current = value; @@ -12,5 +11,5 @@ export const usePrevious = value => { export const useEffectAsync = (effect, deps) => { useEffect(() => { effect(); - }, deps); // eslint-disable-line react-hooks/exhaustive-deps + }, deps); }; diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js index 0a5c8e18..70ce780f 100644 --- a/modern/src/reports/ChartReportPage.js +++ b/modern/src/reports/ChartReportPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -import { Grid, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core'; +import { + Grid, FormControl, InputLabel, Select, MenuItem, +} from '@material-ui/core'; import ReportLayoutPage from './ReportLayoutPage'; import ReportFilter from './ReportFilter'; import Graph from './Graph'; @@ -9,66 +11,61 @@ import { speedFromKnots } from '../common/converter'; import t from '../common/localization'; const Filter = ({ children, setItems }) => { - const speedUnit = useAttributePreference('speedUnit'); const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, mail }); + const query = new URLSearchParams({ + deviceId, from, to, mail, + }); const response = await fetch(`/api/reports/route?${query.toString()}`, { headers }); if (response.ok) { const positions = await response.json(); - let formattedPositions = positions.map(position => { - return { - speed: Number(speedFromKnots(position.speed, speedUnit)), - altitude: position.altitude, - accuracy: position.accuracy, - fixTime: formatDate(position.fixTime) - } - }); + const formattedPositions = positions.map((position) => ({ + speed: Number(speedFromKnots(position.speed, speedUnit)), + altitude: position.altitude, + accuracy: position.accuracy, + fixTime: formatDate(position.fixTime), + })); setItems(formattedPositions); } - } + }; return ( <> <ReportFilter handleSubmit={handleSubmit} showOnly /> {children} </> - ) -} - -const ChartType = ({ type, setType }) => { + ); +}; - return ( - <Grid container spacing={3}> - <Grid item xs={12} sm={6}> - <FormControl variant="filled" margin="normal" fullWidth> - <InputLabel>{t('reportChartType')}</InputLabel> - <Select value={type} onChange={e => setType(e.target.value)}> - <MenuItem value="speed">{t('positionSpeed')}</MenuItem> - <MenuItem value="accuracy">{t('positionAccuracy')}</MenuItem> - <MenuItem value="altitude">{t('positionAltitude')}</MenuItem> - </Select> - </FormControl> - </Grid> +const ChartType = ({ type, setType }) => ( + <Grid container spacing={3}> + <Grid item xs={12} sm={6}> + <FormControl variant="filled" margin="normal" fullWidth> + <InputLabel>{t('reportChartType')}</InputLabel> + <Select value={type} onChange={(e) => setType(e.target.value)}> + <MenuItem value="speed">{t('positionSpeed')}</MenuItem> + <MenuItem value="accuracy">{t('positionAccuracy')}</MenuItem> + <MenuItem value="altitude">{t('positionAltitude')}</MenuItem> + </Select> + </FormControl> </Grid> - ) -} - + </Grid> +); const ChartReportPage = () => { - const [items, setItems] = useState([]); const [type, setType] = useState('speed'); return ( - <ReportLayoutPage filter={ + <ReportLayoutPage filter={( <Filter setItems={setItems}> <ChartType type={type} setType={setType} /> </Filter> - }> + )} + > <Graph items={items} type={type} /> </ReportLayoutPage> - ) -} + ); +}; export default ChartReportPage; diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js index 6d80860c..89efcf76 100644 --- a/modern/src/reports/EventReportPage.js +++ b/modern/src/reports/EventReportPage.js @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { DataGrid } from '@material-ui/data-grid'; -import { Grid, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core'; -import { useTheme } from "@material-ui/core/styles"; +import { + Grid, FormControl, InputLabel, Select, MenuItem, +} from '@material-ui/core'; +import { useTheme } from '@material-ui/core/styles'; import { useSelector } from 'react-redux'; import { formatDate } from '../common/formatter'; import ReportFilter from './ReportFilter'; @@ -10,12 +12,13 @@ import { prefixString } from '../common/stringUtils'; import t from '../common/localization'; const Filter = ({ setItems }) => { - const [eventTypes, setEventTypes] = useState(['allEvents']); const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, mail }); - eventTypes.forEach(it => query.append('type', it)); + const query = new URLSearchParams({ + deviceId, from, to, mail, + }); + eventTypes.forEach((it) => query.append('type', it)); const response = await fetch(`/api/reports/events?${query.toString()}`, { headers }); if (response.ok) { const contentType = response.headers.get('content-type'); @@ -34,7 +37,7 @@ const Filter = ({ setItems }) => { <Grid item xs={12} sm={6}> <FormControl variant="filled" fullWidth> <InputLabel>{t('reportEventTypes')}</InputLabel> - <Select value={eventTypes} onChange={e => setEventTypes(e.target.value)} multiple> + <Select value={eventTypes} onChange={(e) => setEventTypes(e.target.value)} multiple> <MenuItem value="allEvents">{t('eventAll')}</MenuItem> <MenuItem value="deviceOnline">{t('eventDeviceOnline')}</MenuItem> <MenuItem value="deviceUnknown">{t('eventDeviceUnknown')}</MenuItem> @@ -58,21 +61,20 @@ const Filter = ({ setItems }) => { </Grid> </ReportFilter> ); -} +}; const EventReportPage = () => { - const theme = useTheme(); - const geofences = useSelector(state => state.geofences.items); + const geofences = useSelector((state) => state.geofences.items); const [items, setItems] = useState([]); - const formatGeofence = value => { + const formatGeofence = (value) => { if (value > 0) { - const geofence = geofences[value]; - return geofence ? geofence.name : ''; + const geofence = geofences[value]; + return geofence ? geofence.name : ''; } return null; - } + }; const columns = [{ headerName: t('positionFixTime'), @@ -101,12 +103,13 @@ const EventReportPage = () => { return ( <ReportLayoutPage filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter - autoHeight /> + rows={items} + columns={columns} + hideFooter + autoHeight + /> </ReportLayoutPage> ); -} +}; export default EventReportPage; diff --git a/modern/src/reports/Graph.js b/modern/src/reports/Graph.js index 990eb5d5..63d24eee 100644 --- a/modern/src/reports/Graph.js +++ b/modern/src/reports/Graph.js @@ -1,9 +1,11 @@ import React from 'react'; import { withWidth } from '@material-ui/core'; -import {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { + LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, +} from 'recharts'; -const CustomizedAxisTick = ({ x, y, payload }) =>{ - if(!payload.value) { +const CustomizedAxisTick = ({ x, y, payload }) => { + if (!payload.value) { return payload.value; } const parts = payload.value.split(' '); @@ -13,22 +15,19 @@ const CustomizedAxisTick = ({ x, y, payload }) =>{ <text x={0} y={16} dy={16} textAnchor="end" fill="#666" transform="rotate(-35)">{parts[1]}</text> </g> ); -} +}; -const Graph = ({ type, items }) => { - - return ( - <ResponsiveContainer height={400} width="100%" debounce={1}> - <LineChart data={items}> - <XAxis dataKey="fixTime" tick={<CustomizedAxisTick/>} height={60} /> - <YAxis /> - <CartesianGrid strokeDasharray="3 3" /> - <Tooltip /> - <Legend /> - <Line type="natural" dataKey={type} /> - </LineChart> - </ResponsiveContainer> - ); -} +const Graph = ({ type, items }) => ( + <ResponsiveContainer height={400} width="100%" debounce={1}> + <LineChart data={items}> + <XAxis dataKey="fixTime" tick={<CustomizedAxisTick />} height={60} /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Line type="natural" dataKey={type} /> + </LineChart> + </ResponsiveContainer> +); export default withWidth()(Graph); diff --git a/modern/src/reports/ReplayPage.js b/modern/src/reports/ReplayPage.js index dfa99997..12bbd351 100644 --- a/modern/src/reports/ReplayPage.js +++ b/modern/src/reports/ReplayPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -import { Accordion, AccordionDetails, AccordionSummary, Container, makeStyles, Paper, Slider, Tooltip, Typography } from '@material-ui/core'; +import { + Accordion, AccordionDetails, AccordionSummary, Container, makeStyles, Paper, Slider, Tooltip, Typography, +} from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import MainToolbar from '../MainToolbar'; import Map from '../map/Map'; @@ -9,7 +11,7 @@ import PositionsMap from '../map/PositionsMap'; import { formatPosition } from '../common/formatter'; import ReportFilter from './ReportFilter'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { height: '100%', display: 'flex', @@ -31,13 +33,11 @@ const useStyles = makeStyles(theme => ({ }, })); -const TimeLabel = ({ children, open, value }) => { - return ( - <Tooltip open={open} enterTouchDelay={0} placement="top" title={value}> - {children} - </Tooltip> - ); -}; +const TimeLabel = ({ children, open, value }) => ( + <Tooltip open={open} enterTouchDelay={0} placement="top" title={value}> + {children} + </Tooltip> +); const ReplayPage = () => { const classes = useStyles(); @@ -61,12 +61,12 @@ const ReplayPage = () => { <MainToolbar /> <Map> <ReplayPathMap positions={positions} /> - {index < positions.length && - <PositionsMap positions={[positions[index]]} /> - } + {index < positions.length + && <PositionsMap positions={[positions[index]]} />} </Map> <Container maxWidth="sm" className={classes.controlPanel}> - {!!positions.length && + {!!positions.length + && ( <Paper className={classes.controlContent}> <Slider max={positions.length - 1} @@ -75,15 +75,15 @@ const ReplayPage = () => { value={index} onChange={(_, index) => setIndex(index)} valueLabelDisplay="auto" - valueLabelFormat={i => i < positions.length ? formatPosition(positions[i], 'fixTime') : ''} + valueLabelFormat={(i) => (i < positions.length ? formatPosition(positions[i], 'fixTime') : '')} ValueLabelComponent={TimeLabel} - /> + /> </Paper> - } + )} <div> <Accordion expanded={expanded} onChange={() => setExpanded(!expanded)}> <AccordionSummary expandIcon={<ExpandMoreIcon />}> - <Typography align='center'> + <Typography align="center"> {t('reportConfigure')} </Typography> </AccordionSummary> @@ -95,6 +95,6 @@ const ReplayPage = () => { </Container> </div> ); -} +}; export default ReplayPage; diff --git a/modern/src/reports/ReportFilter.js b/modern/src/reports/ReportFilter.js index c92741eb..1e2eb887 100644 --- a/modern/src/reports/ReportFilter.js +++ b/modern/src/reports/ReportFilter.js @@ -1,11 +1,13 @@ import React, { useState } from 'react'; -import { FormControl, InputLabel, Select, MenuItem, Button, TextField, Grid, Typography } from '@material-ui/core'; +import { + FormControl, InputLabel, Select, MenuItem, Button, TextField, Grid, Typography, +} from '@material-ui/core'; import { useSelector } from 'react-redux'; import moment from 'moment'; import t from '../common/localization'; const ReportFilter = ({ children, handleSubmit, showOnly }) => { - const devices = useSelector(state => Object.values(state.devices.items)); + const devices = useSelector((state) => Object.values(state.devices.items)); const [deviceId, setDeviceId] = useState(); const [period, setPeriod] = useState('today'); const [from, setFrom] = useState(moment().subtract(1, 'hour')); @@ -51,9 +53,9 @@ const ReportFilter = ({ children, handleSubmit, showOnly }) => { selectedFrom.toISOString(), selectedTo.toISOString(), mail, - { Accept: accept } + { Accept: accept }, ); - } + }; return ( <Grid container spacing={2}> @@ -81,56 +83,69 @@ const ReportFilter = ({ children, handleSubmit, showOnly }) => { </Select> </FormControl> </Grid> - {period === 'custom' && <Grid item xs={12} sm={3}> + {period === 'custom' && ( + <Grid item xs={12} sm={3}> <TextField variant="filled" label={t('reportFrom')} type="datetime-local" value={from.format(moment.HTML5_FMT.DATETIME_LOCAL)} - onChange={e => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} - fullWidth /> - </Grid>} - {period === 'custom' && <Grid item xs={12} sm={3}> + onChange={(e) => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} + fullWidth + /> + </Grid> + )} + {period === 'custom' && ( + <Grid item xs={12} sm={3}> <TextField variant="filled" label={t('reportTo')} type="datetime-local" value={to.format(moment.HTML5_FMT.DATETIME_LOCAL)} - onChange={e => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} - fullWidth /> - </Grid>} + onChange={(e) => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} + fullWidth + /> + </Grid> + )} {children} <Grid item xs={!showOnly ? 4 : 12} sm={!showOnly ? 2 : 6}> - <Button + <Button onClick={() => handleClick(false, true)} - variant='outlined' - color='secondary' - fullWidth> + variant="outlined" + color="secondary" + fullWidth + > {t('reportShow')} </Button> </Grid> - {!showOnly && + {!showOnly + && ( <Grid item xs={4} sm={2}> - <Button - onClick={() => handleClick(false, false)} - variant='outlined' - color='secondary' - fullWidth> - {t('reportExport')} - </Button> - </Grid>} - {!showOnly && + <Button + onClick={() => handleClick(false, false)} + variant="outlined" + color="secondary" + fullWidth + > + {t('reportExport')} + </Button> + </Grid> + )} + {!showOnly + && ( <Grid item xs={4} sm={2}> - <Button - onClick={() => handleClick(true, false)} - variant='outlined' - color='secondary' - fullWidth> - <Typography variant="button" noWrap>{t('reportEmail')}</Typography> - </Button> - </Grid>} + <Button + onClick={() => handleClick(true, false)} + variant="outlined" + color="secondary" + fullWidth + > + <Typography variant="button" noWrap>{t('reportEmail')}</Typography> + </Button> + </Grid> + )} </Grid> ); -} +}; export default ReportFilter; diff --git a/modern/src/reports/ReportLayoutPage.js b/modern/src/reports/ReportLayoutPage.js index 6bab67c6..d25a8876 100644 --- a/modern/src/reports/ReportLayoutPage.js +++ b/modern/src/reports/ReportLayoutPage.js @@ -1,6 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { Grid, Typography, Divider, Drawer, makeStyles, IconButton, Hidden } from '@material-ui/core'; +import { + Grid, Typography, Divider, Drawer, makeStyles, IconButton, Hidden, +} 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'; @@ -9,11 +11,11 @@ import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted'; import TrendingUpIcon from '@material-ui/icons/TrendingUp'; import ArrowBackIcon from '@material-ui/icons/ArrowBack'; -import ReportSidebar from '../components/reports/ReportSidebar' -import ReportNavbar from '../components/reports/ReportNavbar' +import ReportSidebar from '../components/reports/ReportSidebar'; +import ReportNavbar from '../components/reports/ReportNavbar'; import t from '../common/localization'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { display: 'flex', height: '100%', @@ -23,10 +25,10 @@ const useStyles = makeStyles(theme => ({ }, drawer: { width: theme.dimensions.drawerWidthDesktop, - [theme.breakpoints.down("md")]: { + [theme.breakpoints.down('md')]: { width: theme.dimensions.drawerWidthTablet, - } - }, + }, + }, content: { flex: 1, padding: theme.spacing(5, 3, 3, 3), @@ -39,13 +41,13 @@ const useStyles = makeStyles(theme => ({ }, backArrowIconContainer: { '&:hover': { - backgroundColor:"transparent" - } + backgroundColor: 'transparent', + }, }, toolbar: { - [theme.breakpoints.down("md")]: { + [theme.breakpoints.down('md')]: { ...theme.mixins.toolbar, - } + }, }, })); @@ -58,7 +60,7 @@ const routes = [ { name: t('reportChart'), href: '/reports/chart', icon: <TrendingUpIcon /> }, ]; -const ReportLayoutPage = ({ children, filter, }) => { +const ReportLayoutPage = ({ children, filter }) => { const classes = useStyles(); const history = useHistory(); const location = useLocation(); @@ -66,7 +68,7 @@ const ReportLayoutPage = ({ children, filter, }) => { const [reportTitle, setReportTitle] = useState(); useEffect(() => { - routes.forEach(route => { + routes.forEach((route) => { switch (location.pathname) { case `${route.href}`: setReportTitle(route.name); @@ -85,7 +87,8 @@ const ReportLayoutPage = ({ children, filter, }) => { variant="temporary" open={openDrawer} onClose={() => setOpenDrawer(!openDrawer)} - classes={{paper: classes.drawer}}> + classes={{ paper: classes.drawer }} + > <ReportSidebar routes={routes} /> </Drawer> </Hidden> @@ -93,17 +96,19 @@ const ReportLayoutPage = ({ children, filter, }) => { <div className={classes.drawerContainer}> <Drawer variant="permanent" - classes={{paper: classes.drawer}}> + classes={{ paper: classes.drawer }} + > <div className={classes.drawerHeader}> <IconButton onClick={() => history.push('/')} - className={classes.backArrowIconContainer} - disableRipple> + className={classes.backArrowIconContainer} + disableRipple + > <ArrowBackIcon /> </IconButton> <Typography variant="h6" color="inherit" noWrap> {t('reportTitle')} - </Typography> + </Typography> </div> <Divider /> <ReportSidebar routes={routes} /> @@ -116,9 +121,9 @@ const ReportLayoutPage = ({ children, filter, }) => { <Grid item>{filter}</Grid> <Grid item>{children}</Grid> </Grid> - </div> + </div> </div> ); -} +}; export default ReportLayoutPage; diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index 04b513e9..dce08cb1 100644 --- a/modern/src/reports/RouteReportPage.js +++ b/modern/src/reports/RouteReportPage.js @@ -1,17 +1,20 @@ import React, { useState } from 'react'; import { Paper } from '@material-ui/core'; import { DataGrid } from '@material-ui/data-grid'; -import { useTheme } from "@material-ui/core/styles"; -import { formatDistance, formatSpeed, formatBoolean, formatDate, formatCoordinate } from '../common/formatter'; +import { useTheme } from '@material-ui/core/styles'; +import { + formatDistance, formatSpeed, formatBoolean, formatDate, formatCoordinate, +} from '../common/formatter'; import ReportFilter from './ReportFilter'; import ReportLayoutPage from './ReportLayoutPage'; import { useAttributePreference, usePreference } from '../common/preferences'; import t from '../common/localization'; const Filter = ({ setItems }) => { - const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, mail }); + const query = new URLSearchParams({ + deviceId, from, to, mail, + }); const response = await fetch(`/api/reports/route?${query.toString()}`, { headers }); if (response.ok) { const contentType = response.headers.get('content-type'); @@ -23,7 +26,7 @@ const Filter = ({ setItems }) => { } } } - } + }; return <ReportFilter handleSubmit={handleSubmit} />; }; @@ -78,7 +81,7 @@ const RouteReportPage = () => { width: theme.dimensions.columnWidthNumber, valueGetter: ({ row }) => row.attributes.totalDistance, valueFormatter: ({ value }) => formatDistance(value, distanceUnit), - }] + }]; const [items, setItems] = useState([]); @@ -86,10 +89,11 @@ const RouteReportPage = () => { <ReportLayoutPage filter={<Filter setItems={setItems} />}> <Paper> <DataGrid - rows={items} - columns={columns} - hideFooter - autoHeight /> + rows={items} + columns={columns} + hideFooter + autoHeight + /> </Paper> </ReportLayoutPage> ); diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js index 6953c464..078a5e5e 100644 --- a/modern/src/reports/StopReportPage.js +++ b/modern/src/reports/StopReportPage.js @@ -1,16 +1,19 @@ import React, { useState } from 'react'; import { DataGrid } from '@material-ui/data-grid'; -import { useTheme } from "@material-ui/core/styles"; -import { formatDistance, formatHours, formatDate, formatVolume } from '../common/formatter'; +import { useTheme } from '@material-ui/core/styles'; +import { + formatDistance, formatHours, formatDate, formatVolume, +} from '../common/formatter'; import ReportFilter from './ReportFilter'; import ReportLayoutPage from './ReportLayoutPage'; import { useAttributePreference } from '../common/preferences'; import t from '../common/localization'; const Filter = ({ setItems }) => { - const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, mail }); + const query = new URLSearchParams({ + deviceId, from, to, mail, + }); const response = await fetch(`/api/reports/stops?${query.toString()}`, { headers }); if (response.ok) { const contentType = response.headers.get('content-type'); @@ -22,13 +25,12 @@ const Filter = ({ setItems }) => { } } } - } + }; return <ReportFilter handleSubmit={handleSubmit} />; }; const StopReportPage = () => { - const theme = useTheme(); const distanceUnit = useAttributePreference('distanceUnit'); @@ -41,7 +43,7 @@ const StopReportPage = () => { field: 'startTime', type: 'dateTime', width: theme.dimensions.columnWidthDate, - valueFormatter: ({ value }) => formatDate(value), + valueFormatter: ({ value }) => formatDate(value), }, { headerName: t('positionOdometer'), field: 'startOdometer', @@ -53,7 +55,7 @@ const StopReportPage = () => { field: 'address', type: 'string', hide: true, - width: theme.dimensions.columnWidthString, + width: theme.dimensions.columnWidthString, }, { headerName: t('reportEndTime'), field: 'endTime', @@ -78,17 +80,18 @@ const StopReportPage = () => { type: 'number', width: theme.dimensions.columnWidthNumber, hide: true, - valueFormatter: ({ value }) => formatVolume(value, volumeUnit), - }] - + valueFormatter: ({ value }) => formatVolume(value, volumeUnit), + }]; + return ( <ReportLayoutPage filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayoutPage> ); }; diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js index e3819a53..0e88705f 100644 --- a/modern/src/reports/SummaryReportPage.js +++ b/modern/src/reports/SummaryReportPage.js @@ -1,19 +1,22 @@ import React, { useState } from 'react'; import { DataGrid } from '@material-ui/data-grid'; import { Grid, FormControlLabel, Checkbox } from '@material-ui/core'; -import { useTheme } from "@material-ui/core/styles"; -import { formatDistance, formatHours, formatDate, formatSpeed, formatVolume } from '../common/formatter'; +import { useTheme } from '@material-ui/core/styles'; +import { + formatDistance, formatHours, formatDate, formatSpeed, formatVolume, +} from '../common/formatter'; import ReportFilter from './ReportFilter'; import ReportLayoutPage from './ReportLayoutPage'; import { useAttributePreference } from '../common/preferences'; import t from '../common/localization'; const Filter = ({ setItems }) => { - const [daily, setDaily] = useState(false); const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, daily, mail }); + const query = new URLSearchParams({ + deviceId, from, to, daily, mail, + }); const response = await fetch(`/api/reports/summary?${query.toString()}`, { headers }); if (response.ok) { const contentType = response.headers.get('content-type'); @@ -25,21 +28,21 @@ const Filter = ({ setItems }) => { } } } - } + }; return ( <ReportFilter handleSubmit={handleSubmit}> <Grid item xs={12} sm={6}> <FormControlLabel - control={<Checkbox checked={daily} onChange={e => setDaily(e.target.checked)} />} - label={t('reportDaily')} /> + control={<Checkbox checked={daily} onChange={(e) => setDaily(e.target.checked)} />} + label={t('reportDaily')} + /> </Grid> </ReportFilter> ); -} +}; const SummaryReportPage = () => { - const theme = useTheme(); const distanceUnit = useAttributePreference('distanceUnit'); @@ -96,19 +99,20 @@ const SummaryReportPage = () => { type: 'number', width: theme.dimensions.columnWidthNumber, hide: true, - valueFormatter: ({ value }) => formatVolume(value, volumeUnit), - }] - + valueFormatter: ({ value }) => formatVolume(value, volumeUnit), + }]; + return ( <ReportLayoutPage filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayoutPage> ); -} +}; export default SummaryReportPage; diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js index 5f414f44..c10e6b1a 100644 --- a/modern/src/reports/TripReportPage.js +++ b/modern/src/reports/TripReportPage.js @@ -1,16 +1,19 @@ import React, { useState } from 'react'; import { DataGrid } from '@material-ui/data-grid'; -import { useTheme } from "@material-ui/core/styles"; -import { formatDistance, formatSpeed, formatHours, formatDate, formatVolume } from '../common/formatter'; +import { useTheme } from '@material-ui/core/styles'; +import { + formatDistance, formatSpeed, formatHours, formatDate, formatVolume, +} from '../common/formatter'; import ReportFilter from './ReportFilter'; import ReportLayoutPage from './ReportLayoutPage'; import { useAttributePreference } from '../common/preferences'; import t from '../common/localization'; const Filter = ({ setItems }) => { - const handleSubmit = async (deviceId, from, to, mail, headers) => { - const query = new URLSearchParams({ deviceId, from, to, mail }); + const query = new URLSearchParams({ + deviceId, from, to, mail, + }); const response = await fetch(`/api/reports/trips?${query.toString()}`, { headers }); if (response.ok) { const contentType = response.headers.get('content-type'); @@ -22,15 +25,14 @@ const Filter = ({ setItems }) => { } } } - } + }; return <ReportFilter handleSubmit={handleSubmit} />; -} +}; const TripReportPage = () => { - const theme = useTheme(); - + const distanceUnit = useAttributePreference('distanceUnit'); const speedUnit = useAttributePreference('speedUnit'); const volumeUnit = useAttributePreference('volumeUnit'); @@ -109,19 +111,20 @@ const TripReportPage = () => { field: 'driverName', type: 'string', width: theme.dimensions.columnWidthString, - hide: true - }] + hide: true, + }]; return ( <ReportLayoutPage filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayoutPage> ); -} +}; export default TripReportPage; diff --git a/modern/src/serviceWorker.js b/modern/src/serviceWorker.js index f59f1997..a0344303 100644 --- a/modern/src/serviceWorker.js +++ b/modern/src/serviceWorker.js @@ -11,13 +11,13 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || + window.location.hostname === 'localhost' // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || + || window.location.hostname === '[::1]' // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + || window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, + ), ); export function register(config) { @@ -42,8 +42,8 @@ export function register(config) { // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA', ); }); } else { @@ -57,7 +57,7 @@ export function register(config) { function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) - .then(registration => { + .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { @@ -70,8 +70,8 @@ function registerValidSW(swUrl, config) { // but the previous service worker will still serve the older // content until all client tabs are closed. console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', ); // Execute callback @@ -93,7 +93,7 @@ function registerValidSW(swUrl, config) { }; }; }) - .catch(error => { + .catch((error) => { console.error('Error during service worker registration:', error); }); } @@ -103,15 +103,15 @@ function checkValidServiceWorker(swUrl, config) { fetch(swUrl, { headers: { 'Service-Worker': 'script' }, }) - .then(response => { + .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) + response.status === 404 + || (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload(); }); @@ -123,7 +123,7 @@ function checkValidServiceWorker(swUrl, config) { }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.' + 'No internet connection found. App is running in offline mode.', ); }); } @@ -131,11 +131,11 @@ function checkValidServiceWorker(swUrl, config) { export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready - .then(registration => { + .then((registration) => { registration.unregister(); }) - .catch(error => { + .catch((error) => { console.error(error.message); }); } -}
\ No newline at end of file +} diff --git a/modern/src/settings/ComputedAttributePage.js b/modern/src/settings/ComputedAttributePage.js index 73759fab..fea613a9 100644 --- a/modern/src/settings/ComputedAttributePage.js +++ b/modern/src/settings/ComputedAttributePage.js @@ -1,19 +1,19 @@ import React, { useState } from 'react'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControl, InputLabel, MenuItem, Select, TextField } from "@material-ui/core"; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControl, InputLabel, MenuItem, Select, TextField, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from '../common/localization'; import EditItemView from '../EditItemView'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import positionAttributes from '../attributes/positionAttributes'; - const useStyles = makeStyles(() => ({ details: { flexDirection: 'column', }, })); -const ComputedAttributePage =() => { - +const ComputedAttributePage = () => { const classes = useStyles(); const [item, setItem] = useState(); const [key, setKey] = useState(); @@ -24,20 +24,21 @@ const ComputedAttributePage =() => { type: value.type, })); - const handleChange = event => { + const handleChange = (event) => { const newValue = event.target.value; setKey(newValue); const positionAttribute = positionAttributes[newValue]; - if(positionAttribute && positionAttribute.type) { - setItem({...item, attribute: newValue, type: positionAttribute.type}); + if (positionAttribute && positionAttribute.type) { + setItem({ ...item, attribute: newValue, type: positionAttribute.type }); } else { - setItem({...item, attribute: newValue}); + setItem({ ...item, attribute: newValue }); } - } + }; return ( <EditItemView endpoint="/attributes/computed" item={item} setItem={setItem}> - {item && + {item + && ( <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="subtitle1"> @@ -48,46 +49,51 @@ const ComputedAttributePage =() => { <TextField margin="normal" value={item.description || ''} - onChange={event => setItem({...item, description: event.target.value})} + onChange={(event) => setItem({ ...item, description: event.target.value })} label={t('sharedDescription')} - variant="filled" /> + variant="filled" + /> <FormControl variant="filled" margin="normal" fullWidth> <InputLabel>{t('sharedAttribute')}</InputLabel> - <Select - value={item.attribute || ''} - onChange={handleChange}> + <Select + value={item.attribute || ''} + onChange={handleChange} + > {options.map((option) => ( <MenuItem key={option.key} value={option.key}>{option.name}</MenuItem> ))} </Select> - </FormControl> + </FormControl> <TextField margin="normal" value={item.expression || ''} - onChange={event => setItem({...item, expression: event.target.value})} + onChange={(event) => setItem({ ...item, expression: event.target.value })} label={t('sharedExpression')} multiline rows={4} - variant="filled" /> + variant="filled" + /> <FormControl variant="filled" margin="normal" fullWidth - disabled={key in positionAttributes}> + disabled={key in positionAttributes} + > <InputLabel>{t('sharedType')}</InputLabel> <Select value={item.type || ''} - onChange={event => setItem({...item, type: event.target.value})}> - <MenuItem value={'string'}>{t('sharedTypeString')}</MenuItem> - <MenuItem value={'number'}>{t('sharedTypeNumber')}</MenuItem> - <MenuItem value={'boolean'}>{t('sharedTypeBoolean')}</MenuItem> + onChange={(event) => setItem({ ...item, type: event.target.value })} + > + <MenuItem value="string">{t('sharedTypeString')}</MenuItem> + <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem> + <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem> </Select> </FormControl> </AccordionDetails> </Accordion> - } + )} </EditItemView> - ) -} + ); +}; export default ComputedAttributePage; diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js index 1a6feab5..f555259b 100644 --- a/modern/src/settings/ComputedAttributesPage.js +++ b/modern/src/settings/ComputedAttributesPage.js @@ -1,13 +1,15 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import { useSelector } from 'react-redux'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -18,7 +20,7 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { const classes = useStyles(); const [items, setItems] = useState([]); - const adminEnabled = useSelector(state => state.session.user && state.session.user.administrator); + const adminEnabled = useSelector((state) => state.session.user && state.session.user.administrator); useEffectAsync(async () => { const response = await fetch('/api/attributes/computed'); @@ -29,45 +31,44 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { return ( <TableContainer> - <Table> - <TableHead> - <TableRow> - {adminEnabled && <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}> - {adminEnabled && + <Table> + <TableHead> + <TableRow> + {adminEnabled && <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}> + {adminEnabled + && ( <TableCell className={classes.columnAction} padding="none"> <IconButton 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> - </TableRow> - ))} - </TableBody> - </Table> + )} + <TableCell>{item.description}</TableCell> + <TableCell>{item.attribute}</TableCell> + <TableCell>{item.expression}</TableCell> + <TableCell>{item.type}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const ComputedAttributesPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={ComputedAttributeView} editPath="/settings/attribute" endpoint="attributes/computed" /> - </> - ); -} +const ComputedAttributesPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={ComputedAttributeView} editPath="/settings/attribute" endpoint="attributes/computed" /> + </> +); export default ComputedAttributesPage; diff --git a/modern/src/settings/DriverPage.js b/modern/src/settings/DriverPage.js index 86feab84..01400c5c 100644 --- a/modern/src/settings/DriverPage.js +++ b/modern/src/settings/DriverPage.js @@ -1,9 +1,11 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from '../common/localization'; import EditItemView from '../EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditAttributesView from '../attributes/EditAttributesView'; const useStyles = makeStyles(() => ({ @@ -19,7 +21,8 @@ const DriverPage = () => { return ( <EditItemView endpoint="drivers" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -31,15 +34,17 @@ const DriverPage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> <TextField margin="normal" value={item.uniqueId || ''} - onChange={event => setItem({...item, uniqueId: event.target.value})} + onChange={(event) => setItem({ ...item, uniqueId: event.target.value })} label={t('deviceIdentifier')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -51,15 +56,15 @@ const DriverPage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={{}} - /> + /> </AccordionDetails> </Accordion> </> - } + )} </EditItemView> ); -} +}; export default DriverPage; diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js index 957e2250..36fc12d6 100644 --- a/modern/src/settings/DriversPage.js +++ b/modern/src/settings/DriversPage.js @@ -1,12 +1,14 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -27,39 +29,37 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => { 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 onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.uniqueId}</TableCell> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> + <TableCell>{t('deviceIdentifier')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.uniqueId}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const DriversPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" /> - </> - ); -} +const DriversPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={DriversView} editPath="/settings/driver" endpoint="drivers" /> + </> +); export default DriversPage; diff --git a/modern/src/settings/GroupPage.js b/modern/src/settings/GroupPage.js index b9fa8716..f9af0f8b 100644 --- a/modern/src/settings/GroupPage.js +++ b/modern/src/settings/GroupPage.js @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import TextField from '@material-ui/core/TextField'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t from '../common/localization'; import EditItemView from '../EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import EditAttributesView from '../attributes/EditAttributesView'; import deviceAttributes from '../attributes/deviceAttributes'; import SelectField from '../form/SelectField'; @@ -22,7 +24,8 @@ const GroupPage = () => { return ( <EditItemView endpoint="groups" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -34,9 +37,10 @@ const GroupPage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -49,10 +53,11 @@ const GroupPage = () => { <SelectField margin="normal" value={item.groupId || 0} - onChange={event => setItem({...item, groupId: Number(event.target.value)})} + onChange={(event) => setItem({ ...item, groupId: Number(event.target.value) })} endpoint="/api/groups" label={t('groupParent')} - variant="filled" /> + variant="filled" + /> </AccordionDetails> </Accordion> <Accordion> @@ -64,15 +69,15 @@ const GroupPage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={deviceAttributes} - /> + /> </AccordionDetails> </Accordion> </> - } + )} </EditItemView> ); -} +}; export default GroupPage; diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js index e2740627..d5921844 100644 --- a/modern/src/settings/GroupsPage.js +++ b/modern/src/settings/GroupsPage.js @@ -1,12 +1,14 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -27,37 +29,35 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => { 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 onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedName')}</TableCell> </TableRow> - ))} - </TableBody> - </Table> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const GroupsPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" /> - </> - ); -} +const GroupsPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={GroupsView} editPath="/settings/group" endpoint="groups" /> + </> +); export default GroupsPage; diff --git a/modern/src/settings/MaintenancePage.js b/modern/src/settings/MaintenancePage.js index 3b4fde54..89ebaa12 100644 --- a/modern/src/settings/MaintenancePage.js +++ b/modern/src/settings/MaintenancePage.js @@ -1,14 +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'; import t from '../common/localization'; import { prefixString } from '../common/stringUtils'; import EditItemView from '../EditItemView'; -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'; import EditAttributesView from '../attributes/EditAttributesView'; import positionAttributes from '../attributes/positionAttributes'; import { useAttributePreference } from '../common/preferences'; -import { speedFromKnots, speedToKnots, distanceFromMeters, distanceToMeters } from '../common/converter'; +import { + speedFromKnots, speedToKnots, distanceFromMeters, distanceToMeters, +} from '../common/converter'; const useStyles = makeStyles(() => ({ details: { @@ -17,46 +21,46 @@ const useStyles = makeStyles(() => ({ })); const MaintenancePage = () => { - const classes = useStyles(); const [item, setItem] = useState(); - const [labels, setLabels] = useState({start: '', period: ''}); + const [labels, setLabels] = useState({ start: '', period: '' }); const speedUnit = useAttributePreference('speedUnit'); const distanceUnit = useAttributePreference('distanceUnit'); const convertToList = (attributes) => { - let otherList = []; - for (const key in attributes) { + const otherList = []; + Object.keys(attributes).forEach((key) => { const value = attributes[key]; if (value.type === 'number') { - otherList.push({key, name: value.name, type: value.type}); + otherList.push({ key, name: value.name, type: value.type }); } - } + }); return otherList; - } + }; - const onMaintenanceTypeChange = event => { + const onMaintenanceTypeChange = (event) => { const newValue = event.target.value; - setItem({ ...item, type: newValue, start: 0, period: 0 }); + setItem({ + ...item, type: newValue, start: 0, period: 0, + }); const attribute = positionAttributes[newValue]; if (attribute && attribute.dataType) { switch (attribute.dataType) { case 'distance': - setLabels({ ...labels, start: t(prefixString('shared', distanceUnit)), period: t(prefixString('shared', distanceUnit))}); + setLabels({ ...labels, start: t(prefixString('shared', distanceUnit)), period: t(prefixString('shared', distanceUnit)) }); break; case 'speed': - setLabels({ ...labels, start: t(prefixString('shared', speedUnit)), period: t(prefixString('shared', speedUnit))}); + setLabels({ ...labels, start: t(prefixString('shared', speedUnit)), period: t(prefixString('shared', speedUnit)) }); break; default: break; } } - } - - const rawToValue = value => { + }; + const rawToValue = (value) => { const attribute = positionAttributes[item.type]; if (attribute && attribute.dataType) { switch (attribute.dataType) { @@ -69,10 +73,9 @@ const MaintenancePage = () => { } } return value; - } - - const valueToRaw = value => { + }; + const valueToRaw = (value) => { const attribute = positionAttributes[item.type]; if (attribute && attribute.dataType) { switch (attribute.dataType) { @@ -85,11 +88,12 @@ const MaintenancePage = () => { } } return value; - } + }; return ( <EditItemView endpoint="maintenance" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -101,39 +105,43 @@ const MaintenancePage = () => { <TextField margin="normal" value={item.name || ''} - onChange={event => setItem({...item, name: event.target.value})} + onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} - variant="filled" /> + variant="filled" + /> <FormControl variant="filled" margin="normal" fullWidth> <InputLabel>{t('sharedType')}</InputLabel> - <Select - value={item.type || ''} - onChange={onMaintenanceTypeChange}> - {convertToList(positionAttributes).map(({ key, name })=>( - <MenuItem key={key} value={key}>{name}</MenuItem> - ))} + <Select + value={item.type || ''} + onChange={onMaintenanceTypeChange} + > + {convertToList(positionAttributes).map(({ key, name }) => ( + <MenuItem key={key} value={key}>{name}</MenuItem> + ))} </Select> - </FormControl> + </FormControl> <TextField margin="normal" type="number" value={rawToValue(item.start) || ''} - onChange={event => setItem({...item, start: valueToRaw(event.target.value)})} + onChange={(event) => setItem({ ...item, start: valueToRaw(event.target.value) })} label={t('maintenanceStart')} variant="filled" InputProps={{ - endAdornment: <InputAdornment position="start">{labels.start}</InputAdornment>, - }} /> + endAdornment: <InputAdornment position="start">{labels.start}</InputAdornment>, + }} + /> <TextField margin="normal" type="number" value={rawToValue(item.period) || ''} - onChange={event => setItem({...item, period: valueToRaw(event.target.value)})} + onChange={(event) => setItem({ ...item, period: valueToRaw(event.target.value) })} label={t('maintenancePeriod')} variant="filled" InputProps={{ - endAdornment: <InputAdornment position="start">{labels.period}</InputAdornment>, - }} /> + endAdornment: <InputAdornment position="start">{labels.period}</InputAdornment>, + }} + /> </AccordionDetails> </Accordion> <Accordion> @@ -145,15 +153,15 @@ const MaintenancePage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({...item, attributes})} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={{}} - /> + /> </AccordionDetails> - </Accordion> + </Accordion> </> - } + )} </EditItemView> ); -} +}; export default MaintenancePage; diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js index 7ba4bd29..e598bc90 100644 --- a/modern/src/settings/MaintenancesPage.js +++ b/modern/src/settings/MaintenancesPage.js @@ -1,7 +1,9 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; @@ -10,7 +12,7 @@ import positionAttributes from '../attributes/positionAttributes'; import { formatDistance, formatSpeed } from '../common/formatter'; import { useAttributePreference } from '../common/preferences'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -37,7 +39,7 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { switch (attribute.dataType) { case 'speed': return formatSpeed(value, speedUnit); - case 'distance': + case 'distance': return formatDistance(value, distanceUnit); default: return value; @@ -45,47 +47,45 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { } return value; - } + }; 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 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> + <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> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton 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> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const MaintenacesPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" /> - </> - ); -} +const MaintenacesPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={MaintenancesView} editPath="/settings/maintenance" endpoint="maintenance" /> + </> +); export default MaintenacesPage; diff --git a/modern/src/settings/NotificationPage.js b/modern/src/settings/NotificationPage.js index 33904e7f..11c427fe 100644 --- a/modern/src/settings/NotificationPage.js +++ b/modern/src/settings/NotificationPage.js @@ -1,9 +1,11 @@ import React, { useState } from 'react'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import t, { findStringKeys } from '../common/localization'; import EditItemView from '../EditItemView'; -import { Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, FormControlLabel, Checkbox } from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { prefixString, unprefixString } from '../common/stringUtils'; import SelectField from '../form/SelectField'; @@ -18,14 +20,15 @@ const NotificationPage = () => { const [item, setItem] = useState(); - const alarms = findStringKeys(it => it.startsWith('alarm')).map(it => ({ + const alarms = findStringKeys((it) => it.startsWith('alarm')).map((it) => ({ key: unprefixString('alarm', it), name: t(it), })); return ( <EditItemView endpoint="notifications" item={item} setItem={setItem}> - {item && + {item + && ( <> <Accordion defaultExpanded> <AccordionSummary expandIcon={<ExpandMoreIcon />}> @@ -38,47 +41,52 @@ const NotificationPage = () => { margin="normal" value={item.type || 'alarm'} emptyValue={null} - onChange={e => setItem({...item, type: e.target.value})} + onChange={(e) => setItem({ ...item, type: e.target.value })} endpoint="/api/notifications/types" - keyGetter={it => it.type} - titleGetter={it => t(prefixString('event', it.type))} + keyGetter={(it) => it.type} + titleGetter={(it) => t(prefixString('event', it.type))} label={t('sharedType')} - variant="filled" /> + variant="filled" + /> <SelectField multiple margin="normal" value={item.notificators ? item.notificators.split(/[, ]+/) : []} - onChange={e => setItem({...item, notificators: e.target.value.join()})} + onChange={(e) => setItem({ ...item, notificators: e.target.value.join() })} endpoint="/api/notifications/notificators" - keyGetter={it => it.type} - titleGetter={it => t(prefixString('notificator', it.type))} + keyGetter={(it) => it.type} + titleGetter={(it) => t(prefixString('notificator', it.type))} label={t('notificationNotificators')} - variant="filled" /> - {(!item.type || item.type === 'alarm') && + 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()}})} + onChange={(e) => setItem({ ...item, attributes: { ...item.attributes, alarms: e.target.value.join() } })} data={alarms} - keyGetter={it => it.key} + keyGetter={(it) => it.key} label={t('sharedAlarms')} - variant="filled" /> - } + variant="filled" + /> + )} <FormControlLabel - control={ + control={( <Checkbox checked={item.always} - onChange={event => setItem({...item, always: event.target.checked})} - /> - } - label={t('notificationAlways')} /> + onChange={(event) => setItem({ ...item, always: event.target.checked })} + /> + )} + label={t('notificationAlways')} + /> </AccordionDetails> </Accordion> </> - } + )} </EditItemView> ); -} +}; export default NotificationPage; diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js index 15da0de7..4e052927 100644 --- a/modern/src/settings/NotificationsPage.js +++ b/modern/src/settings/NotificationsPage.js @@ -1,14 +1,16 @@ import React, { useState } from 'react'; -import MainToolbar from '../MainToolbar'; -import { TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton } from '@material-ui/core'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import MainToolbar from '../MainToolbar'; import t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; import { prefixString } from '../common/stringUtils'; import { formatBoolean } from '../common/formatter'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), padding: theme.spacing(0, 1), @@ -32,7 +34,7 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => { return value .split(/[, ]+/) .filter(Boolean) - .map(it => t(prefixString(prefix, it))) + .map((it) => t(prefixString(prefix, it))) .join(', '); } return ''; @@ -40,43 +42,41 @@ 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 onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{t(prefixString('event', item.type))}</TableCell> - <TableCell>{formatBoolean(item.always)}</TableCell> - <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> - <TableCell>{formatList('notificator', item.notificators)}</TableCell> + <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> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{t(prefixString('event', item.type))}</TableCell> + <TableCell>{formatBoolean(item.always)}</TableCell> + <TableCell>{formatList('alarm', item.attributes.alarms)}</TableCell> + <TableCell>{formatList('notificator', item.notificators)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); -} +}; -const NotificationsPage = () => { - return ( - <> - <MainToolbar /> - <EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" /> - </> - ); -} +const NotificationsPage = () => ( + <> + <MainToolbar /> + <EditCollectionView content={NotificationsView} editPath="/settings/notification" endpoint="notifications" /> + </> +); export default NotificationsPage; diff --git a/modern/src/setupProxy.js b/modern/src/setupProxy.js index c16655c8..3ab7643e 100644 --- a/modern/src/setupProxy.js +++ b/modern/src/setupProxy.js @@ -1,6 +1,8 @@ +/* eslint-disable import/no-extraneous-dependencies */ + const proxy = require('http-proxy-middleware'); -module.exports = function (app) { - app.use(proxy('/api/socket', { target: 'ws://' + process.env.REACT_APP_URL_NAME, ws: true })); - app.use(proxy('/api', { target: 'http://' + process.env.REACT_APP_URL_NAME })); +export default (app) => { + app.use(proxy('/api/socket', { target: `ws://${process.env.REACT_APP_URL_NAME}`, ws: true })); + app.use(proxy('/api', { target: `http://${process.env.REACT_APP_URL_NAME}` })); }; diff --git a/modern/src/store/devices.js b/modern/src/store/devices.js index 66c1607f..cca23cb9 100644 --- a/modern/src/store/devices.js +++ b/modern/src/store/devices.js @@ -4,15 +4,15 @@ const { reducer, actions } = createSlice({ name: 'devices', initialState: { items: {}, - selectedId: null + selectedId: null, }, reducers: { refresh(state, action) { state.items = {}; - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, update(state, action) { - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, select(state, action) { state.selectedId = action.payload.id; @@ -20,7 +20,7 @@ const { reducer, actions } = createSlice({ remove(state, action) { delete state.items[action.payload]; }, - } + }, }); export { actions as devicesActions }; diff --git a/modern/src/store/drivers.js b/modern/src/store/drivers.js index 63522d78..38933d84 100644 --- a/modern/src/store/drivers.js +++ b/modern/src/store/drivers.js @@ -7,9 +7,9 @@ const { reducer, actions } = createSlice({ }, reducers: { update(state, action) { - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, - } + }, }); export { actions as driversActions }; diff --git a/modern/src/store/geofences.js b/modern/src/store/geofences.js index 504b7d02..f2b7666a 100644 --- a/modern/src/store/geofences.js +++ b/modern/src/store/geofences.js @@ -8,12 +8,12 @@ const { reducer, actions } = createSlice({ reducers: { refresh(state, action) { state.items = {}; - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, update(state, action) { - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, - } + }, }); export { actions as geofencesActions }; diff --git a/modern/src/store/groups.js b/modern/src/store/groups.js index 483323f3..11fc5dbf 100644 --- a/modern/src/store/groups.js +++ b/modern/src/store/groups.js @@ -7,9 +7,9 @@ const { reducer, actions } = createSlice({ }, reducers: { update(state, action) { - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, - } + }, }); export { actions as groupsActions }; diff --git a/modern/src/store/maintenances.js b/modern/src/store/maintenances.js index 0813f6b6..08b2adb6 100644 --- a/modern/src/store/maintenances.js +++ b/modern/src/store/maintenances.js @@ -7,9 +7,9 @@ const { reducer, actions } = createSlice({ }, reducers: { update(state, action) { - action.payload.forEach(item => state.items[item['id']] = item); + action.payload.forEach((item) => state.items[item.id] = item); }, - } + }, }); export { actions as maintenancesActions }; diff --git a/modern/src/store/positions.js b/modern/src/store/positions.js index 1df468cd..8858298d 100644 --- a/modern/src/store/positions.js +++ b/modern/src/store/positions.js @@ -7,9 +7,9 @@ const { reducer, actions } = createSlice({ }, reducers: { update(state, action) { - action.payload.forEach(item => state.items[item['deviceId']] = item); + action.payload.forEach((item) => state.items[item.deviceId] = item); }, - } + }, }); export { actions as positionsActions }; diff --git a/modern/src/theme/dimensions.js b/modern/src/theme/dimensions.js index a2403abe..fcdbaee5 100644 --- a/modern/src/theme/dimensions.js +++ b/modern/src/theme/dimensions.js @@ -8,5 +8,5 @@ export default { columnWidthDate: 160, columnWidthNumber: 130, columnWidthString: 160, - columnWidthBoolean: 130 + columnWidthBoolean: 130, }; diff --git a/modern/src/theme/index.js b/modern/src/theme/index.js index 5a3b2a9c..dc0a35bf 100644 --- a/modern/src/theme/index.js +++ b/modern/src/theme/index.js @@ -6,7 +6,7 @@ import dimensions from './dimensions'; const theme = createMuiTheme({ palette, overrides, - dimensions + dimensions, }); export default theme; diff --git a/modern/src/theme/overrides.js b/modern/src/theme/overrides.js index cd6c5a5a..a6d08cf1 100644 --- a/modern/src/theme/overrides.js +++ b/modern/src/theme/overrides.js @@ -4,17 +4,17 @@ export default { MuiFormControl: { root: { marginTop: 5, - marginBottom: 5 - } + marginBottom: 5, + }, }, MuiInputLabel: { filled: { transform: 'translate(12px, 14px) scale(1)', - '&$shrink' :{ - transform: 'translate(12px, -14px) scale(0.72)' - } + '&$shrink': { + transform: 'translate(12px, -14px) scale(0.72)', + }, }, - }, + }, MuiFilledInput: { root: { height: dimensions.inputHeight, @@ -28,19 +28,19 @@ export default { boxSizing: 'border-box', '&:-webkit-autofill': { WebkitBoxShadow: '0 0 0 100px #eeeeee inset', - }, + }, }, underline: { - "&:before": { + '&:before': { borderBottom: 'none', }, - "&:after": { + '&:after': { borderBottom: 'none', }, - "&:hover:before": { + '&:hover:before': { borderBottom: 'none', - }, - } + }, + }, }, MuiButton: { root: { @@ -48,31 +48,31 @@ export default { marginTop: 5, marginBottom: 5, '&$disabled': { - opacity: .4, + opacity: 0.4, color: undefined, - } + }, }, contained: { '&$disabled': { - opacity: .4, + opacity: 0.4, color: undefined, - backgroundColor: undefined - } - } + backgroundColor: undefined, + }, + }, }, MuiFormHelperText: { root: { - marginBottom: -10 + marginBottom: -10, }, contained: { - marginLeft: 12 - } + marginLeft: 12, + }, }, MuiAutocomplete: { inputRoot: { '&.MuiFilledInput-root': { - paddingTop: 0 - } - } - } + paddingTop: 0, + }, + }, + }, }; |