diff options
Diffstat (limited to 'modern/src')
83 files changed, 1502 insertions, 1389 deletions
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..a20ba222 100644 --- a/modern/src/DevicePage.js +++ b/modern/src/DevicePage.js @@ -1,10 +1,12 @@ 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'; @@ -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 bab56c7f..a77f578e 100644 --- a/modern/src/EditItemView.js +++ b/modern/src/EditItemView.js @@ -9,20 +9,22 @@ import t from './common/localization'; import { useEffectAsync } from './reactHelper'; import OptionsLayout from './settings/OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { - marginTop: theme.spacing(2) + marginTop: theme.spacing(2), }, buttons: { display: 'flex', justifyContent: 'space-evenly', '& > *': { - flexBasis: '33%' - } - } + flexBasis: '33%', + }, + }, })); -const EditItemView = ({ children, endpoint, item, setItem }) => { +const EditItemView = ({ + children, endpoint, item, setItem, +}) => { const history = useHistory(); const classes = useStyles(); const { id } = useParams(); @@ -47,7 +49,7 @@ const EditItemView = ({ children, endpoint, item, setItem }) => { const response = await fetch(url, { method: !id ? 'POST' : 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(item) + body: JSON.stringify(item), }); if (response.ok) { 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 59b8459a..71219c16 100644 --- a/modern/src/GeofencesPage.js +++ b/modern/src/GeofencesPage.js @@ -1,21 +1,22 @@ import React from 'react'; -import { Divider, isWidthUp, makeStyles, withWidth, Typography, IconButton } from '@material-ui/core'; +import { + Divider, isWidthUp, makeStyles, withWidth, Typography, IconButton, +} from '@material-ui/core'; import Drawer from '@material-ui/core/Drawer'; import ContainerDimensions from 'react-container-dimensions'; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; import Map from './map/Map'; import CurrentLocationMap from './map/CurrentLocationMap'; import GeofenceEditMap from './map/GeofenceEditMap'; import GeofencesList from './GeofencesList'; -import ArrowBackIcon from '@material-ui/icons/ArrowBack'; - import t from './common/localization'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { height: '100%', display: 'flex', - flexDirection: 'column' + flexDirection: 'column', }, content: { flexGrow: 1, @@ -23,27 +24,27 @@ const useStyles = makeStyles(theme => ({ display: 'flex', flexDirection: 'row', [theme.breakpoints.down('xs')]: { - flexDirection: 'column-reverse' - } + flexDirection: 'column-reverse', + }, }, drawerPaper: { position: 'relative', [theme.breakpoints.up('sm')]: { - width: 350 + width: 350, }, [theme.breakpoints.down('xs')]: { - height: 250 - } + height: 250, + }, }, drawerHeader: { ...theme.mixins.toolbar, display: 'flex', alignItems: 'center', - padding: theme.spacing(0, 1) + padding: theme.spacing(0, 1), }, mapContainer: { - flexGrow: 1 - } + flexGrow: 1, + }, })); const GeofencesPage = ({ width }) => { 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 311f024d..9a72729c 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'; @@ -20,23 +19,24 @@ import MapIcon from '@material-ui/icons/Map'; import PersonIcon from '@material-ui/icons/Person'; import DescriptionIcon from '@material-ui/icons/Description'; import ReplayIcon from '@material-ui/icons/Replay'; +import { sessionActions } from './store'; import t from './common/localization'; import * as selectors from './selectors'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ flex: { - flexGrow: 1 + flexGrow: 1, }, appBar: { - zIndex: theme.zIndex.drawer + 1 + zIndex: theme.zIndex.drawer + 1, }, list: { - width: 250 + width: 250, }, menuButton: { marginLeft: -12, - marginRight: 20 - } + marginRight: 20, + }, })); const MainToolbar = () => { @@ -111,7 +111,7 @@ const MainToolbar = () => { <ListItem button disabled={!userId} - onClick={() => history.push(`/settings/notifications`)} + onClick={() => history.push('/settings/notifications')} > <ListItemIcon> <PersonIcon /> diff --git a/modern/src/PositionPage.js b/modern/src/PositionPage.js index a91a7a15..f1766f65 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}; + 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..c4b99f3e 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,80 +14,79 @@ 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> - ) - + ); } }; 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..901f5060 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") { +const displayNotifications = (events) => { + if ('Notification' in window) { + if (Notification.permission === 'granted') { for (const event of events) { 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 53f8c354..1472f501 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 c33a6316..6d5b575a 100644 --- a/modern/src/admin/ServerPage.js +++ b/modern/src/admin/ServerPage.js @@ -1,31 +1,33 @@ 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 { sessionActions } from '../store'; import EditAttributesView from '../attributes/EditAttributesView'; import deviceAttributes from '../attributes/deviceAttributes'; import userAttributes from '../attributes/userAttributes'; import OptionsLayout from '../settings/OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { - marginTop: theme.spacing(2) + marginTop: theme.spacing(2), }, buttons: { display: 'flex', justifyContent: 'space-evenly', '& > *': { - flexBasis: '33%' - } + flexBasis: '33%', + }, }, details: { - flexDirection: 'column' - } + flexDirection: 'column', + }, })); const ServerPage = () => { @@ -33,15 +35,14 @@ const ServerPage = () => { const dispatch = useDispatch(); const classes = useStyles(); - const item = useSelector(state => state.session.server); - const setItem = updatedItem => - dispatch(sessionActions.updateServer(updatedItem)); + const item = useSelector((state) => state.session.server); + const setItem = (updatedItem) => dispatch(sessionActions.updateServer(updatedItem)); const handleSave = async () => { const response = await fetch('/api/server', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(item) + body: JSON.stringify(item), }); if (response.ok) { @@ -64,9 +65,7 @@ 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" /> @@ -80,53 +79,45 @@ const ServerPage = () => { </AccordionSummary> <AccordionDetails className={classes.details}> <FormControlLabel - control={ + control={( <Checkbox checked={item.registration} - onChange={event => - setItem({ ...item, registration: event.target.checked }) - } + onChange={(event) => setItem({ ...item, registration: event.target.checked })} /> - } + )} label={t('serverRegistration')} /> <FormControlLabel - control={ + control={( <Checkbox checked={item.readonly} - onChange={event => - setItem({ ...item, readonly: event.target.checked }) - } + onChange={(event) => setItem({ ...item, readonly: event.target.checked })} /> - } + )} label={t('serverReadonly')} /> <FormControlLabel - control={ + control={( <Checkbox checked={item.deviceReadonly} - onChange={event => - setItem({ - ...item, - deviceReadonly: event.target.checked - }) - } + onChange={(event) => setItem({ + ...item, + deviceReadonly: event.target.checked, + })} /> - } + )} label={t('userDeviceReadonly')} /> <FormControlLabel - control={ + control={( <Checkbox checked={item.limitCommands} - onChange={event => - setItem({ - ...item, - limitCommands: event.target.checked - }) - } + onChange={(event) => setItem({ + ...item, + limitCommands: event.target.checked, + })} /> - } + )} label={t('userLimitCommands')} /> </AccordionDetails> @@ -140,9 +131,9 @@ const ServerPage = () => { <AccordionDetails className={classes.details}> <EditAttributesView attributes={item.attributes} - setAttributes={attributes => setItem({ ...item, attributes })} + setAttributes={(attributes) => setItem({ ...item, attributes })} definitions={{ ...userAttributes, ...deviceAttributes }} - /> + /> </AccordionDetails> </Accordion> </> diff --git a/modern/src/admin/StatisticsPage.js b/modern/src/admin/StatisticsPage.js index b1504c87..8a6e6013 100644 --- a/modern/src/admin/StatisticsPage.js +++ b/modern/src/admin/StatisticsPage.js @@ -1,9 +1,10 @@ - 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 moment from 'moment'; import OptionsLayout from '../settings/OptionsLayout'; const Filter = ({ setItems }) => { @@ -59,10 +60,10 @@ const Filter = ({ setItems }) => { const query = new URLSearchParams({ from: selectedFrom.toISOString(), - to: selectedTo.toISOString() + to: selectedTo.toISOString(), }); const response = await fetch(`/api/statistics?${query.toString()}`, { - Accept: 'application/json' + Accept: 'application/json', }); if (response.ok) { setItems(await response.json()); @@ -73,7 +74,7 @@ const Filter = ({ setItems }) => { <> <FormControl variant="filled" margin="normal" fullWidth> <InputLabel>{t('reportPeriod')}</InputLabel> - <Select value={period} onChange={e => setPeriod(e.target.value)}> + <Select value={period} onChange={(e) => setPeriod(e.target.value)}> <MenuItem value="today">{t('reportToday')}</MenuItem> <MenuItem value="yesterday">{t('reportYesterday')}</MenuItem> <MenuItem value="thisWeek">{t('reportThisWeek')}</MenuItem> @@ -90,9 +91,7 @@ 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)) - } + onChange={(e) => setFrom(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} fullWidth /> )} @@ -103,9 +102,7 @@ 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)) - } + onChange={(e) => setTo(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL))} fullWidth /> )} @@ -117,7 +114,7 @@ const Filter = ({ setItems }) => { > {t('reportShow')} </Button> - </> + </> ); }; @@ -144,7 +141,7 @@ const StatisticsPage = () => { </TableRow> </TableHead> <TableBody> - {items.map(item => ( + {items.map((item) => ( <TableRow key={item.id}> <TableCell>{formatDate(item.captureTime)}</TableCell> <TableCell>{item.activeUsers}</TableCell> @@ -155,7 +152,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> diff --git a/modern/src/admin/UsersPage.js b/modern/src/admin/UsersPage.js index ceaab6ab..6afe099e 100644 --- a/modern/src/admin/UsersPage.js +++ b/modern/src/admin/UsersPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; @@ -7,11 +9,11 @@ import EditCollectionView from '../EditCollectionView'; import { formatBoolean } from '../common/formatter'; import OptionsLayout from '../settings/OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const UsersView = ({ updateTimestamp, onMenuClick }) => { @@ -28,48 +30,46 @@ 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"> + <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)} + 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> + <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 ( - <OptionsLayout> - <EditCollectionView - content={UsersView} - editPath="/user" - endpoint="users" - /> - </OptionsLayout> - ); -}; +const UsersPage = () => ( + <OptionsLayout> + <EditCollectionView + content={UsersView} + editPath="/user" + endpoint="users" + /> + </OptionsLayout> +); 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..5ca7cad1 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), @@ -23,8 +24,8 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { const [addDialogShown, setAddDialogShown] = useState(false); const convertToList = (attributes) => { - let booleanList = []; - let otherList = []; + const booleanList = []; + const otherList = []; for (const key in attributes) { const value = attributes[key]; const type = getAttributeType(value); @@ -35,12 +36,12 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { } } 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,20 +49,20 @@ 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}; + const updatedAttributes = { ...attributes }; updatedAttributes[key] = value; setAttributes(updatedAttributes); }; const deleteAttribute = (key) => { - let updatedAttributes = {...attributes}; + const updatedAttributes = { ...attributes }; delete updatedAttributes[key]; setAttributes(updatedAttributes); }; @@ -74,11 +75,10 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { const getAttributeType = (value) => { if (typeof value === 'number') { return 'number'; - } else if (typeof value === 'boolean') { + } if (typeof value === 'boolean') { return 'boolean'; - } else { - return 'string'; } + 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/formatter.js b/modern/src/common/formatter.js index 0d8ac4c9..7441d01e 100644 --- a/modern/src/common/formatter.js +++ b/modern/src/common/formatter.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import t from '../common/localization'; +import t from './localization'; export const formatPosition = (value, key) => { if (value != null && typeof value === 'object') { @@ -18,29 +18,22 @@ 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 formatBoolean = (value) => (value ? t('sharedYes') : t('sharedNo')); -export const formatNumber = (value, precision = 1) => { - return Number(value.toFixed(precision)); -}; +export const formatNumber = (value, precision = 1) => Number(value.toFixed(precision)); -export const formatDate = (value, format = 'YYYY-MM-DD HH:mm') => { - return moment(value).format(format); -}; +export const formatDate = (value, format = 'YYYY-MM-DD HH:mm') => moment(value).format(format); export const formatDistance = (value, unit) => { switch (unit) { @@ -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/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/NavBar.js b/modern/src/components/NavBar.js index 68e38bf0..93e79bbb 100644 --- a/modern/src/components/NavBar.js +++ b/modern/src/components/NavBar.js @@ -1,5 +1,7 @@ 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'; const Navbar = ({ setOpenDrawer, title }) => ( diff --git a/modern/src/components/SideNav.js b/modern/src/components/SideNav.js index 8bd43ac2..aa078084 100644 --- a/modern/src/components/SideNav.js +++ b/modern/src/components/SideNav.js @@ -1,5 +1,7 @@ import React from 'react'; -import { List, ListItem, ListItemText, ListItemIcon, Divider, ListSubheader } from '@material-ui/core'; +import { + List, ListItem, ListItemText, ListItemIcon, Divider, ListSubheader, +} from '@material-ui/core'; import { Link, useLocation } from 'react-router-dom'; const SideNav = ({ routes }) => { @@ -7,26 +9,24 @@ const SideNav = ({ routes }) => { return ( <List disablePadding style={{ paddingTop: '16px' }}> - {routes.map((route, index) => - route.subheader ? ( - <> - <Divider /> - <ListSubheader>{route.subheader}</ListSubheader> - </> - ) : ( - <ListItem - disableRipple - component={Link} - key={route.href || route.subheader} - button - to={route.href} - selected={location.pathname.match(route.match || route.href)} - > - <ListItemIcon>{route.icon}</ListItemIcon> - <ListItemText primary={route.name} /> - </ListItem> - ) - )} + {routes.map((route, index) => (route.subheader ? ( + <> + <Divider /> + <ListSubheader>{route.subheader}</ListSubheader> + </> + ) : ( + <ListItem + disableRipple + component={Link} + key={route.href || route.subheader} + button + to={route.href} + selected={location.pathname.match(route.match || route.href)} + > + <ListItemIcon>{route.icon}</ListItemIcon> + <ListItemText primary={route.name} /> + </ListItem> + )))} </List> ); }; 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/form/LinkField.js b/modern/src/form/LinkField.js index b228fb34..26e1471e 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,28 +30,28 @@ 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))) { + for (const added of newValue.filter((it) => !oldValue.includes(it))) { await 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))) { + for (const removed of oldValue.filter((it) => !newValue.includes(it))) { await fetch('/api/permissions', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, @@ -66,16 +68,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..f24fcb32 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}`, { @@ -100,6 +100,6 @@ const GeofenceEditMap = () => { }, [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..672bd260 100644 --- a/modern/src/map/Map.js +++ b/modern/src/map/Map.js @@ -1,8 +1,10 @@ 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 { prepareIcon, loadImage } from './mapUtil'; import { styleCarto, styleMapbox, styleOsm } from './mapStyles'; @@ -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,7 +44,7 @@ const initMap = async () => { map.addImage('background', await prepareIcon(background), { pixelRatio: window.devicePixelRatio, }); - await Promise.all(deviceCategories.map(async category => { + 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]), { @@ -93,7 +95,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/reactHelper.js b/modern/src/reactHelper.js index b0eb0169..d8e92004 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; diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js index 8eeedc4b..c55ed6d5 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 ReportLayout from './ReportLayout'; 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 ( - <ReportLayout filter={ + <ReportLayout filter={( <Filter setItems={setItems}> <ChartType type={type} setType={setType} /> </Filter> - }> + )} + > <Graph items={items} type={type} /> </ReportLayout> - ) -} + ); +}; export default ChartReportPage; diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js index b938dc85..b5c3daeb 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 ( <ReportLayout filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter - autoHeight /> + rows={items} + columns={columns} + hideFooter + autoHeight + /> </ReportLayout> ); -} +}; 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 524f7bbf..dec8d018 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} justify="flex-end"> @@ -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/ReportLayout.js b/modern/src/reports/ReportLayout.js index 5b543355..92ffc0c9 100644 --- a/modern/src/reports/ReportLayout.js +++ b/modern/src/reports/ReportLayout.js @@ -7,7 +7,7 @@ import { Drawer, makeStyles, IconButton, - Hidden + Hidden, } from '@material-ui/core'; import TimelineIcon from '@material-ui/icons/Timeline'; import PauseCircleFilledIcon from '@material-ui/icons/PauseCircleFilled'; @@ -21,35 +21,35 @@ import SideNav from '../components/SideNav'; import NavBar from '../components/NavBar'; import t from '../common/localization'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { display: 'flex', - height: '100%' + height: '100%', }, drawerContainer: { - width: theme.dimensions.drawerWidthDesktop + width: theme.dimensions.drawerWidthDesktop, }, drawer: { width: theme.dimensions.drawerWidthDesktop, [theme.breakpoints.down('md')]: { - width: theme.dimensions.drawerWidthTablet - } + width: theme.dimensions.drawerWidthTablet, + }, }, content: { flex: 1, - padding: theme.spacing(5, 3, 3, 3) + padding: theme.spacing(5, 3, 3, 3), }, drawerHeader: { ...theme.mixins.toolbar, display: 'flex', alignItems: 'center', - padding: theme.spacing(0, 1) + padding: theme.spacing(0, 1), }, toolbar: { [theme.breakpoints.down('md')]: { - ...theme.mixins.toolbar - } - } + ...theme.mixins.toolbar, + }, + }, })); const routes = [ @@ -57,24 +57,24 @@ const routes = [ { name: t('reportEvents'), href: '/reports/event', - icon: <NotificationsActiveIcon /> + icon: <NotificationsActiveIcon />, }, { name: t('reportTrips'), href: '/reports/trip', - icon: <PlayCircleFilledIcon /> + icon: <PlayCircleFilledIcon />, }, { name: t('reportStops'), href: '/reports/stop', - icon: <PauseCircleFilledIcon /> + icon: <PauseCircleFilledIcon />, }, { name: t('reportSummary'), href: '/reports/summary', - icon: <FormatListBulletedIcon /> + icon: <FormatListBulletedIcon />, }, - { name: t('reportChart'), href: '/reports/chart', icon: <TrendingUpIcon /> } + { name: t('reportChart'), href: '/reports/chart', icon: <TrendingUpIcon /> }, ]; const ReportLayout = ({ children, filter }) => { @@ -85,7 +85,7 @@ const ReportLayout = ({ children, filter }) => { const [reportTitle, setReportTitle] = useState(); useEffect(() => { - routes.forEach(route => { + routes.forEach((route) => { switch (location.pathname) { case `${route.href}`: setReportTitle(route.name); diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js index c09d4f5c..fffcdcb5 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 ReportLayout from './ReportLayout'; 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 = () => { <ReportLayout filter={<Filter setItems={setItems} />}> <Paper> <DataGrid - rows={items} - columns={columns} - hideFooter - autoHeight /> + rows={items} + columns={columns} + hideFooter + autoHeight + /> </Paper> </ReportLayout> ); diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js index 57f59564..d2e7e7ed 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 ReportLayout from './ReportLayout'; 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 ( <ReportLayout filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayout> ); }; diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js index 53e697d0..4523e652 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 ReportLayout from './ReportLayout'; 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 ( <ReportLayout filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayout> ); -} +}; export default SummaryReportPage; diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js index e8c91295..f611dde1 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 ReportLayout from './ReportLayout'; 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 ( <ReportLayout filter={<Filter setItems={setItems} />}> <DataGrid - rows={items} - columns={columns} - hideFooter + rows={items} + columns={columns} + hideFooter autoHeight - getRowId={() => Math.random()} /> + getRowId={() => Math.random()} + /> </ReportLayout> ); -} +}; export default TripReportPage; 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 d7475989..fcf49d3a 100644 --- a/modern/src/settings/ComputedAttributesPage.js +++ b/modern/src/settings/ComputedAttributesPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; @@ -7,11 +9,11 @@ import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; import OptionsLayout from './OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { @@ -19,7 +21,7 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => { const [items, setItems] = useState([]); const adminEnabled = useSelector( - state => state.session.user && state.session.user.administrator + (state) => state.session.user && state.session.user.administrator, ); useEffectAsync(async () => { @@ -31,50 +33,48 @@ 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}> + <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)} - > + <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 ( - <OptionsLayout> - <EditCollectionView - content={ComputedAttributeView} - editPath="/settings/attribute" - endpoint="attributes/computed" - /> - </OptionsLayout> - ); -}; +const ComputedAttributesPage = () => ( + <OptionsLayout> + <EditCollectionView + content={ComputedAttributeView} + editPath="/settings/attribute" + endpoint="attributes/computed" + /> + </OptionsLayout> +); 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 d5427b2f..57f69b6e 100644 --- a/modern/src/settings/DriversPage.js +++ b/modern/src/settings/DriversPage.js @@ -1,16 +1,18 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; import OptionsLayout from './OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const DriversView = ({ updateTimestamp, onMenuClick }) => { @@ -27,44 +29,42 @@ 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"> + <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)} + onClick={(event) => onMenuClick(event.currentTarget, item.id)} > - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - <TableCell>{item.uniqueId}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.name}</TableCell> + <TableCell>{item.uniqueId}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); }; -const DriversPage = () => { - return ( - <OptionsLayout> - <EditCollectionView - content={DriversView} - editPath="/settings/driver" - endpoint="drivers" - /> - </OptionsLayout> - ); -}; +const DriversPage = () => ( + <OptionsLayout> + <EditCollectionView + content={DriversView} + editPath="/settings/driver" + endpoint="drivers" + /> + </OptionsLayout> +); 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 2fc65c12..3b1058e3 100644 --- a/modern/src/settings/GroupsPage.js +++ b/modern/src/settings/GroupsPage.js @@ -1,16 +1,18 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; import EditCollectionView from '../EditCollectionView'; import OptionsLayout from './OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const GroupsView = ({ updateTimestamp, onMenuClick }) => { @@ -27,42 +29,40 @@ 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"> + <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)} + onClick={(event) => onMenuClick(event.currentTarget, item.id)} > - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{item.name}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.name}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); }; -const GroupsPage = () => { - return ( - <OptionsLayout> - <EditCollectionView - content={GroupsView} - editPath="/settings/group" - endpoint="groups" - /> - </OptionsLayout> - ); -}; +const GroupsPage = () => ( + <OptionsLayout> + <EditCollectionView + content={GroupsView} + editPath="/settings/group" + endpoint="groups" + /> + </OptionsLayout> +); export default GroupsPage; diff --git a/modern/src/settings/MaintenancePage.js b/modern/src/settings/MaintenancePage.js index 3b4fde54..9d60937d 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 = []; + const otherList = []; for (const key in attributes) { 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 d713eaa3..b8e74d28 100644 --- a/modern/src/settings/MaintenancesPage.js +++ b/modern/src/settings/MaintenancesPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; @@ -10,11 +12,11 @@ import { formatDistance, formatSpeed } from '../common/formatter'; import { useAttributePreference } from '../common/preferences'; import OptionsLayout from './OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { @@ -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; @@ -49,48 +51,46 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => { return ( <TableContainer> - <Table> - <TableHead> - <TableRow> - <TableCell className={classes.columnAction} /> - <TableCell>{t('sharedName')}</TableCell> - <TableCell>{t('sharedType')}</TableCell> - <TableCell>{t('maintenanceStart')}</TableCell> - <TableCell>{t('maintenancePeriod')}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {items.map(item => ( - <TableRow key={item.id}> - <TableCell className={classes.columnAction} padding="none"> + <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)} + 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> + <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 ( - <OptionsLayout> - <EditCollectionView - content={MaintenancesView} - editPath="/settings/maintenance" - endpoint="maintenance" - /> - </OptionsLayout> - ); -}; +const MaintenacesPage = () => ( + <OptionsLayout> + <EditCollectionView + content={MaintenancesView} + editPath="/settings/maintenance" + endpoint="maintenance" + /> + </OptionsLayout> +); 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 5707f890..079f88d2 100644 --- a/modern/src/settings/NotificationsPage.js +++ b/modern/src/settings/NotificationsPage.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; -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 t from '../common/localization'; import { useEffectAsync } from '../reactHelper'; @@ -8,11 +10,11 @@ import { prefixString } from '../common/stringUtils'; import { formatBoolean } from '../common/formatter'; import OptionsLayout from './OptionsLayout'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ columnAction: { width: theme.spacing(1), - padding: theme.spacing(0, 1) - } + padding: theme.spacing(0, 1), + }, })); const NotificationsView = ({ updateTimestamp, onMenuClick }) => { @@ -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,52 +42,50 @@ 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"> + <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)} + onClick={(event) => onMenuClick(event.currentTarget, item.id)} > - <MoreVertIcon /> - </IconButton> - </TableCell> - <TableCell>{t(prefixString('event', item.type))}</TableCell> - <TableCell>{formatBoolean(item.always)}</TableCell> + <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> + </TableRow> + ))} + </TableBody> + </Table> </TableContainer> ); }; -const NotificationsPage = () => { - return ( - <OptionsLayout> - <EditCollectionView - content={NotificationsView} - editPath="/settings/notification" - endpoint="notifications" - /> - </OptionsLayout> - ); -}; +const NotificationsPage = () => ( + <OptionsLayout> + <EditCollectionView + content={NotificationsView} + editPath="/settings/notification" + endpoint="notifications" + /> + </OptionsLayout> +); export default NotificationsPage; diff --git a/modern/src/settings/OptionsLayout/useRoutes.js b/modern/src/settings/OptionsLayout/useRoutes.js index 901719f8..f2603dd8 100644 --- a/modern/src/settings/OptionsLayout/useRoutes.js +++ b/modern/src/settings/OptionsLayout/useRoutes.js @@ -13,7 +13,7 @@ import t from '../../common/localization'; const accountRoute = { name: t('settingsUser'), - icon: <PersonIcon /> + icon: <PersonIcon />, }; const adminRoutes = [ @@ -21,18 +21,18 @@ const adminRoutes = [ { name: t('settingsServer'), href: '/admin/server', - icon: <StorageIcon /> + icon: <StorageIcon />, }, { name: t('settingsUsers'), href: '/admin/users', - icon: <PeopleIcon /> + icon: <PeopleIcon />, }, { name: t('statisticsTitle'), href: '/admin/statistics', - icon: <BarChartIcon /> - } + icon: <BarChartIcon />, + }, ]; const mainRoutes = [ @@ -41,38 +41,38 @@ const mainRoutes = [ match: 'geofence', name: t('sharedGeofences'), href: '/geofences', - icon: <CreateIcon /> + icon: <CreateIcon />, }, { match: 'notification', name: t('sharedNotifications'), href: '/settings/notifications', - icon: <NotificationsIcon /> + icon: <NotificationsIcon />, }, { match: 'group', name: t('settingsGroups'), href: '/settings/groups', - icon: <FolderIcon /> + icon: <FolderIcon />, }, { match: 'driver', name: t('sharedDrivers'), href: '/settings/drivers', - icon: <PersonIcon /> + icon: <PersonIcon />, }, { match: 'attribute', name: t('sharedComputedAttributes'), href: '/settings/attributes', - icon: <StorageIcon /> + icon: <StorageIcon />, }, { match: 'maintenance', name: t('sharedMaintenance'), href: '/settings/maintenances', - icon: <BuildIcon /> - } + icon: <BuildIcon />, + }, ]; export default () => { @@ -81,6 +81,6 @@ export default () => { accountRoute.href = `/user/${userId}`; return useMemo(() => [...mainRoutes, ...(isAdmin ? adminRoutes : [])], [ - isAdmin + isAdmin, ]); }; diff --git a/modern/src/setupProxy.js b/modern/src/setupProxy.js index c16655c8..e5ca76d7 100644 --- a/modern/src/setupProxy.js +++ b/modern/src/setupProxy.js @@ -1,6 +1,6 @@ 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 })); + 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, + }, + }, + }, }; |