diff options
author | Anton Tananaev <anton@traccar.org> | 2022-08-06 07:24:13 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-08-06 07:24:13 -0700 |
commit | 6975055fd388c813e730a9be65f6fc96ad5f213e (patch) | |
tree | f81d93689b32235edd9ea5493aeb90bbedcd1e3c | |
parent | 7347b398aae694c58feb1a48944778a7c581dc72 (diff) | |
download | trackermap-web-6975055fd388c813e730a9be65f6fc96ad5f213e.tar.gz trackermap-web-6975055fd388c813e730a9be65f6fc96ad5f213e.tar.bz2 trackermap-web-6975055fd388c813e730a9be65f6fc96ad5f213e.zip |
Support direct calendar creation
-rw-r--r-- | modern/src/settings/CalendarPage.js | 132 | ||||
-rw-r--r-- | web/l10n/en.json | 14 |
2 files changed, 137 insertions, 9 deletions
diff --git a/modern/src/settings/CalendarPage.js b/modern/src/settings/CalendarPage.js index 27fdb462..862aa9a3 100644 --- a/modern/src/settings/CalendarPage.js +++ b/modern/src/settings/CalendarPage.js @@ -1,7 +1,8 @@ +import moment from 'moment'; import React, { useState } from 'react'; import TextField from '@mui/material/TextField'; import { - Accordion, AccordionSummary, AccordionDetails, Typography, + Accordion, AccordionSummary, AccordionDetails, Typography, FormControl, InputLabel, Select, MenuItem, } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -10,6 +11,49 @@ import EditItemView from './components/EditItemView'; import EditAttributesAccordion from './components/EditAttributesAccordion'; import { useTranslation } from '../common/components/LocalizationProvider'; import SettingsMenu from './components/SettingsMenu'; +import { prefixString } from '../common/util/stringUtils'; + +const formatTime = (time) => { + const tzid = Intl.DateTimeFormat().resolvedOptions().timeZone; + return `TZID=${tzid}:${time.format('YYYYMMDDTHHmmss')}`; +}; + +const parseRule = (rule) => { + const fragments = rule.split(';'); + const frequency = fragments[0].includes('FREQ') ? fragments[0].substring(11) : 'ONCE'; + const by = fragments.length > 1 ? fragments[1].split('=')[1].split(',') : null; + return { frequency, by }; +}; + +const formatRule = (rule) => { + const by = rule.by && rule.by.join(','); + switch (rule.frequency) { + case 'DAILY': + return `RRULE:FREQ=${rule.frequency}`; + case 'WEEKLY': + return `RRULE:FREQ=${rule.frequency};BYDAY=${by || 'SU'}`; + case 'MONTHLY': + return `RRULE:FREQ=${rule.frequency};BYMONTHDAY=${by || 1}`; + default: + return 'RRULE:'; + } +}; + +const updateCalendar = (lines, index, element) => window.btoa(lines.map((e, i) => (i !== index ? e : element)).join('\n')); + +const simpleCalendar = () => window.btoa([ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Traccar//NONSGML Traccar//EN', + 'BEGIN:VEVENT', + 'UID:00000000-0000-0000-0000-000000000000', + `DTSTART;${formatTime(moment())}`, + `DTEND;${formatTime(moment().add(1, 'hours'))}`, + 'RRULE:FREQ=DAILY', + 'SUMMARY:Event', + 'END:VEVENT', + 'END:VCALENDAR', +].join('\n')); const useStyles = makeStyles((theme) => ({ details: { @@ -26,6 +70,14 @@ const CalendarPage = () => { const [item, setItem] = useState(); + const decoded = item && item.data && window.atob(item.data); + + const simple = decoded && decoded.indexOf('//Traccar//') > 0; + + const lines = decoded && decoded.split('\n'); + + const rule = simple && parseRule(lines[7]); + const handleFiles = (files) => { if (files.length > 0) { const reader = new FileReader(); @@ -34,8 +86,6 @@ const CalendarPage = () => { setItem({ ...item, data: result.substr(result.indexOf(',') + 1) }); }; reader.readAsDataURL(files[0]); - } else { - setItem({ ...item, data: null }); } }; @@ -46,6 +96,7 @@ const CalendarPage = () => { endpoint="calendars" item={item} setItem={setItem} + defaultItem={{ data: simpleCalendar() }} validate={validate} menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedCalendar']} @@ -64,12 +115,75 @@ const CalendarPage = () => { onChange={(event) => setItem({ ...item, name: event.target.value })} label={t('sharedName')} /> - <DropzoneArea - dropzoneText={t('sharedDropzoneText')} - filesLimit={1} - onChange={handleFiles} - showAlerts={false} - /> + <FormControl> + <InputLabel>{t('sharedType')}</InputLabel> + <Select + label={t('sharedType')} + value={simple ? 'simple' : 'custom'} + onChange={(e) => setItem({ ...item, data: (e.target.value === 'simple' ? simpleCalendar() : null) })} + > + <MenuItem value="simple">{t('calendarSimple')}</MenuItem> + <MenuItem value="custom">{t('reportCustom')}</MenuItem> + </Select> + </FormControl> + {simple ? ( + <> + <TextField + label={t('reportFrom')} + type="datetime-local" + value={moment(lines[5].slice(-15)).locale('en').format(moment.HTML5_FMT.DATETIME_LOCAL)} + onChange={(e) => { + const time = formatTime(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL)); + setItem({ ...item, data: updateCalendar(lines, 5, `DTSTART;${time}`) }); + }} + /> + <TextField + label={t('reportTo')} + type="datetime-local" + value={moment(lines[6].slice(-15)).locale('en').format(moment.HTML5_FMT.DATETIME_LOCAL)} + onChange={(e) => { + const time = formatTime(moment(e.target.value, moment.HTML5_FMT.DATETIME_LOCAL)); + setItem({ ...item, data: updateCalendar(lines, 6, `DTEND;${time}`) }); + }} + /> + <FormControl> + <InputLabel>{t('calendarRecurrence')}</InputLabel> + <Select + label={t('calendarRecurrence')} + value={rule.frequency} + onChange={(e) => setItem({ ...item, data: updateCalendar(lines, 7, formatRule({ frequency: e.target.value })) })} + > + {['ONCE', 'DAILY', 'WEEKLY', 'MONTHLY'].map((it) => ( + <MenuItem key={it} value={it}>{t(prefixString('calendar', it.toLowerCase()))}</MenuItem> + ))} + </Select> + </FormControl> + {['WEEKLY', 'MONTHLY'].includes(rule.frequency) && ( + <FormControl> + <InputLabel>{t('calendarDays')}</InputLabel> + <Select + multiple + label={t('calendarDays')} + value={rule.by} + onChange={(e) => setItem({ ...item, data: updateCalendar(lines, 7, formatRule({ ...rule, by: e.target.value })) })} + > + {rule.frequency === 'WEEKLY' ? ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].map((it) => ( + <MenuItem key={it} value={it.substring(0, 2).toUpperCase()}>{t(prefixString('calendar', it))}</MenuItem> + )) : Array.from({ length: 31 }, (_, i) => i + 1).map((it) => ( + <MenuItem key={it} value={it}>{it}</MenuItem> + ))} + </Select> + </FormControl> + )} + </> + ) : ( + <DropzoneArea + dropzoneText={t('sharedDropzoneText')} + filesLimit={1} + onChange={handleFiles} + showAlerts={false} + /> + )} </AccordionDetails> </Accordion> <EditAttributesAccordion diff --git a/web/l10n/en.json b/web/l10n/en.json index c23cb402..2f2ee27b 100644 --- a/web/l10n/en.json +++ b/web/l10n/en.json @@ -96,6 +96,20 @@ "sharedImport": "Import", "sharedColumns": "Columns", "sharedDropzoneText": "Drag and drop a file here or click", + "calendarSimple": "Simple", + "calendarRecurrence": "Recurrence", + "calendarOnce": "Once", + "calendarDaily": "Daily", + "calendarWeekly": "Weekly", + "calendarMonthly": "Monthly", + "calendarDays": "Days", + "calendarSunday": "Sunday", + "calendarMonday": "Monday", + "calendarTuesday": "Tuesday", + "calendarWednesday": "Wednesday", + "calendarThursday": "Thursday", + "calendarFriday": "Friday", + "calendarSaturday": "Saturday", "attributeSpeedLimit": "Speed Limit", "attributeFuelDropThreshold": "Fuel Drop Threshold", "attributeFuelIncreaseThreshold": "Fuel Increase Threshold", |