import React, { useState } from 'react'; import moment from 'moment'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import { Accordion, AccordionSummary, AccordionDetails, Typography, Container, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, FormGroup, InputAdornment, IconButton, OutlinedInput, Autocomplete, TextField, createFilterOptions, Button, } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import CachedIcon from '@mui/icons-material/Cached'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { useLocalization, useTranslation, useTranslationKeys } from '../common/components/LocalizationProvider'; import usePersistedState from '../common/util/usePersistedState'; import PageLayout from '../common/components/PageLayout'; import SettingsMenu from './components/SettingsMenu'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; import { prefixString, unprefixString } from '../common/util/stringUtils'; import SelectField from '../common/components/SelectField'; import useMapStyles from '../map/core/useMapStyles'; import useMapOverlays from '../map/overlay/useMapOverlays'; import { useCatch } from '../reactHelper'; import { sessionActions } from '../store'; const deviceFields = [ { id: 'name', name: 'sharedName' }, { id: 'uniqueId', name: 'deviceIdentifier' }, { id: 'phone', name: 'sharedPhone' }, { id: 'model', name: 'deviceModel' }, { id: 'contact', name: 'deviceContact' }, { id: 'geofenceIds', name: 'sharedGeofences' }, ]; const useStyles = makeStyles((theme) => ({ container: { marginTop: theme.spacing(2), }, buttons: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), display: 'flex', justifyContent: 'space-evenly', '& > *': { flexBasis: '33%', }, }, details: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), paddingBottom: theme.spacing(3), }, tokenActions: { display: 'flex', flexDirection: 'column', }, })); const PreferencesPage = () => { const classes = useStyles(); const dispatch = useDispatch(); const navigate = useNavigate(); const t = useTranslation(); const user = useSelector((state) => state.session.user); const [attributes, setAttributes] = useState(user.attributes); const { languages, language, setLanguage } = useLocalization(); const languageList = Object.entries(languages).map((values) => ({ code: values[0], name: values[1].name })); const [token, setToken] = useState(null); const [tokenExpiration, setTokenExpiration] = useState(moment().add(1, 'week').locale('en').format(moment.HTML5_FMT.DATE)); const mapStyles = useMapStyles(); const [activeMapStyles, setActiveMapStyles] = usePersistedState('activeMapStyles', ['locationIqStreets', 'osm', 'carto']); const mapOverlays = useMapOverlays(); const [selectedMapOverlay, setSelectedMapOverlay] = usePersistedState('selectedMapOverlay'); const positionAttributes = usePositionAttributes(t); const [positionItems, setPositionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']); const [mapLiveRoutes, setMapLiveRoutes] = usePersistedState('mapLiveRoutes', false); const [mapFollow, setMapFollow] = usePersistedState('mapFollow', false); const [mapCluster, setMapCluster] = usePersistedState('mapCluster', true); const [mapOnSelect, setMapOnSelect] = usePersistedState('mapOnSelect', true); const filter = createFilterOptions(); const generateToken = useCatch(async () => { const expiration = moment(tokenExpiration, moment.HTML5_FMT.DATE).toISOString(); const response = await fetch('/api/session/token', { method: 'POST', body: new URLSearchParams(`expiration=${expiration}`), }); if (response.ok) { setToken(await response.text()); } else { throw Error(await response.text()); } }); const alarms = useTranslationKeys((it) => it.startsWith('alarm')).map((it) => ({ key: unprefixString('alarm', it), name: t(it), })); const [devicePrimary, setDevicePrimary] = usePersistedState('devicePrimary', 'name'); const [deviceSecondary, setDeviceSecondary] = usePersistedState('deviceSecondary', ''); const [soundEvents, setSoundEvents] = usePersistedState('soundEvents', []); const [soundAlarms, setSoundAlarms] = usePersistedState('soundAlarms', ['sos']); const handleSave = useCatch(async () => { const response = await fetch(`/api/users/${user.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...user, attributes }), }); if (response.ok) { dispatch(sessionActions.updateUser(await response.json())); navigate(-1); } else { throw Error(await response.text()); } }); return ( } breadcrumbs={['settingsTitle', 'sharedPreferences']}> }> {t('sharedPreferences')} {t('loginLanguage')} setLanguage(e.target.value)} > {languageList.map((it) => {it.name})} }> {t('userToken')} { setTokenExpiration(e.target.value); setToken(null); }} /> navigator.clipboard.writeText(token)} disabled={!token}> )} /> }> {t('mapTitle')} {t('mapActive')} { const clicked = mapStyles.find((s) => s.id === child.props.value); if (clicked.available) { setActiveMapStyles(e.target.value); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${user.id}?${query.toString()}`); } }} multiple > {mapStyles.map((style) => ( {style.title} ))} {t('mapOverlay')} { const clicked = mapOverlays.find((o) => o.id === e.target.value); if (!clicked || clicked.available) { setSelectedMapOverlay(e.target.value); } else if (clicked.id !== 'custom') { const query = new URLSearchParams({ attribute: clicked.attribute }); navigate(`/settings/user/${user.id}?${query.toString()}`); } }} > {'\u00a0'} {mapOverlays.map((overlay) => ( {overlay.title} ))} (positionAttributes.hasOwnProperty(option) ? positionAttributes[option].name : option)} value={positionItems} onChange={(_, option) => { setPositionItems(option); }} filterOptions={(options, params) => { const filtered = filter(options, params); if (params.inputValue && !filtered.includes(params.inputValue)) { filtered.push(params.inputValue); } return filtered; }} renderInput={(params) => ( )} /> setAttributes({ ...attributes, mapGeofences: e.target.checked })} /> )} label={t('attributeShowGeofences')} /> setMapLiveRoutes(e.target.checked)} />} label={t('mapLiveRoutes')} /> setMapFollow(e.target.checked)} />} label={t('deviceFollow')} /> setMapCluster(e.target.checked)} />} label={t('mapClustering')} /> setMapOnSelect(e.target.checked)} />} label={t('mapOnSelect')} /> }> {t('deviceTitle')} setDevicePrimary(e.target.value)} data={deviceFields} titleGetter={(it) => t(it.name)} label={t('sharedPrimary')} /> setDeviceSecondary(e.target.value)} data={deviceFields} titleGetter={(it) => t(it.name)} label={t('sharedSecondary')} /> }> {t('sharedSound')} setSoundEvents(e.target.value)} endpoint="/api/notifications/types" keyGetter={(it) => it.type} titleGetter={(it) => t(prefixString('event', it.type))} label={t('reportEventTypes')} /> setSoundAlarms(e.target.value)} data={alarms} keyGetter={(it) => it.key} label={t('sharedAlarms')} /> navigate(-1)} > {t('sharedCancel')} {t('sharedSave')} ); }; export default PreferencesPage;