add ADD and put in a validator as cmd can't be empty

This commit is contained in:
Proddy
2023-02-25 18:11:37 +01:00
parent 50400459ee
commit 9bae82592f
2 changed files with 97 additions and 55 deletions

View File

@@ -1,5 +1,4 @@
import { FC, useState, useEffect, useCallback } from 'react'; import { FC, useState, useEffect, useCallback } from 'react';
import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { unstable_useBlocker as useBlocker } from 'react-router-dom';
import { import {
@@ -27,6 +26,7 @@ import CheckIcon from '@mui/icons-material/Check';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import DoneIcon from '@mui/icons-material/Done'; import DoneIcon from '@mui/icons-material/Done';
import AddIcon from '@mui/icons-material/Add';
import { import {
ValidatedTextField, ValidatedTextField,
@@ -41,9 +41,13 @@ import {
import * as EMSESP from './api'; import * as EMSESP from './api';
import { extractErrorMessage, updateValue } from 'utils'; import { extractErrorMessage, updateValue } from 'utils';
import { validate } from 'validators';
import { schedulerItemValidation } from './validators';
import { ScheduleItem, ScheduleFlag } from './types'; import { ScheduleItem, ScheduleFlag } from './types';
import Schema, { ValidateFieldsError } from 'async-validator';
import { useI18nContext } from 'i18n/i18n-react'; import { useI18nContext } from 'i18n/i18n-react';
export const APIURL = window.location.origin + '/api/'; export const APIURL = window.location.origin + '/api/';
@@ -56,24 +60,27 @@ const SettingsScheduler: FC = () => {
const [numChanges, setNumChanges] = useState<number>(0); const [numChanges, setNumChanges] = useState<number>(0);
const blocker = useBlocker(numChanges !== 0); const blocker = useBlocker(numChanges !== 0);
const [errorMessage, setErrorMessage] = useState<string>();
const emptySchedule = { const emptySchedule = {
id: '', id: '0',
active: false, active: false,
deleted: false, deleted: false,
flags: 0, flags: 0,
time: '', time: '12:00',
cmd: '', cmd: '',
value: '', value: '',
description: '' description: ''
}; };
const [schedule, setSchedule] = useState<ScheduleItem[]>([emptySchedule]); const [schedule, setSchedule] = useState<ScheduleItem[]>([emptySchedule]);
const [scheduleItem, setScheduleItem] = useState<ScheduleItem>(); const [scheduleItem, setScheduleItem] = useState<ScheduleItem>();
const [ntpAvailable, setNTPavailable] = useState<boolean>(true); const [ntpAvailable, setNTPavailable] = useState<boolean>(true);
const [dow, setDow] = useState<string[]>([]); const [dow, setDow] = useState<string[]>([]);
const [errorMessage, setErrorMessage] = useState<string>();
const [creating, setCreating] = useState<boolean>(false);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
// eslint-disable-next-line
const [flags, setFlags] = useState(() => ['']);
function getDayNames() { function getDayNames() {
const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });
@@ -84,9 +91,6 @@ const SettingsScheduler: FC = () => {
return days.map((date) => formatter.format(date)); return days.map((date) => formatter.format(date));
} }
// eslint-disable-next-line
const [flags, setFlags] = useState(() => ['']);
useEffect(() => { useEffect(() => {
setNumChanges(getNumChanges()); setNumChanges(getNumChanges());
}); });
@@ -238,15 +242,15 @@ const SettingsScheduler: FC = () => {
ntp_available: ntpAvailable, ntp_available: ntpAvailable,
schedule: schedule schedule: schedule
.filter((si) => !si.deleted) .filter((si) => !si.deleted)
.map((new_si) => { .map((condensed_si) => {
return { return {
id: new_si.id, id: condensed_si.id, // will be ignored
active: new_si.active, active: condensed_si.active,
flags: new_si.flags, flags: condensed_si.flags,
time: new_si.time, time: condensed_si.time,
cmd: new_si.cmd, cmd: condensed_si.cmd,
value: new_si.value, value: condensed_si.value,
description: new_si.description description: condensed_si.description
}; };
}) })
}); });
@@ -301,28 +305,41 @@ const SettingsScheduler: FC = () => {
si.description = ''; si.description = '';
} }
setScheduleItem(si); setScheduleItem(si);
setCreating(false);
};
function makeid() {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < 4) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
const addScheduleItem = () => {
setScheduleItem({
id: makeid(), // random ID of 4 strings
active: false,
deleted: false,
flags: 0,
time: '12:00',
cmd: '',
value: '',
description: ''
});
setCreating(true);
}; };
const updateScheduleItem = () => { const updateScheduleItem = () => {
if (scheduleItem) { if (scheduleItem) {
setSchedule((prevState) => { const new_schedule = [...schedule.filter((si) => si.id !== scheduleItem.id), scheduleItem];
const newState = prevState.map((obj) => { setSchedule(new_schedule);
if (obj.id === scheduleItem.id) { setScheduleItem(undefined);
return {
...obj,
active: scheduleItem.active,
time: scheduleItem.time,
cmd: scheduleItem.cmd,
value: scheduleItem.value,
description: scheduleItem.description
};
}
return obj;
});
return newState;
});
} }
setScheduleItem(undefined);
}; };
const renderSchedule = () => { const renderSchedule = () => {
@@ -401,12 +418,24 @@ const SettingsScheduler: FC = () => {
updateScheduleItem(); updateScheduleItem();
}; };
const validateScheduleItem = async () => {
if (scheduleItem) {
try {
setFieldErrors(undefined);
await validate(schedulerItemValidation(scheduleItem, creating), scheduleItem);
updateScheduleItem();
} catch (errors: any) {
setFieldErrors(errors);
}
}
};
const renderEditSchedule = () => { const renderEditSchedule = () => {
if (scheduleItem) { if (scheduleItem) {
return ( return (
<Dialog open={!!scheduleItem} onClose={() => setScheduleItem(undefined)}> <Dialog open={!!scheduleItem} onClose={() => setScheduleItem(undefined)}>
<DialogTitle> <DialogTitle>
{LL.EDIT() + {(creating ? LL.ADD(0) : LL.EDIT()) +
' ' + ' ' +
((scheduleItem.flags & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER ((scheduleItem.flags & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER
? LL.TIMER() ? LL.TIMER()
@@ -428,9 +457,8 @@ const SettingsScheduler: FC = () => {
control={<Checkbox checked={scheduleItem.active} onChange={updateValue(setScheduleItem)} name="active" />} control={<Checkbox checked={scheduleItem.active} onChange={updateValue(setScheduleItem)} name="active" />}
label={LL.ACTIVE()} label={LL.ACTIVE()}
/> />
{(scheduleItem.flags & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER ? ( {(scheduleItem.flags & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER ? (
<ValidatedTextField <TextField
name="time" name="time"
label={LL.TIMER()} label={LL.TIMER()}
value={scheduleItem.time} value={scheduleItem.time}
@@ -443,7 +471,7 @@ const SettingsScheduler: FC = () => {
<MenuItem value={'00:00'}>{LL.SCHEDULE_TIMER_1()}</MenuItem> <MenuItem value={'00:00'}>{LL.SCHEDULE_TIMER_1()}</MenuItem>
<MenuItem value={'00:01'}>{LL.SCHEDULE_TIMER_2()}</MenuItem> <MenuItem value={'00:01'}>{LL.SCHEDULE_TIMER_2()}</MenuItem>
<MenuItem value={'01:00'}>{LL.SCHEDULE_TIMER_3()}</MenuItem> <MenuItem value={'01:00'}>{LL.SCHEDULE_TIMER_3()}</MenuItem>
</ValidatedTextField> </TextField>
) : ( ) : (
<TextField <TextField
name="time" name="time"
@@ -454,9 +482,9 @@ const SettingsScheduler: FC = () => {
onChange={updateValue(setScheduleItem)} onChange={updateValue(setScheduleItem)}
/> />
)} )}
<ValidatedTextField
<TextField fieldErrors={fieldErrors}
name="command" name="cmd"
label={LL.COMMAND()} label={LL.COMMAND()}
fullWidth fullWidth
value={scheduleItem.cmd} value={scheduleItem.cmd}
@@ -474,16 +502,18 @@ const SettingsScheduler: FC = () => {
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Box flexGrow={1} sx={{ '& button': { mt: 0 } }}> {!creating && (
<Button <Box flexGrow={1} sx={{ '& button': { mt: 0 } }}>
startIcon={<RemoveIcon />} <Button
variant="outlined" startIcon={<RemoveIcon />}
color="error" variant="outlined"
onClick={() => removeScheduleItem(scheduleItem)} color="error"
> onClick={() => removeScheduleItem(scheduleItem)}
{LL.REMOVE()} >
</Button> {LL.REMOVE()}
</Box> </Button>
</Box>
)}
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
variant="outlined" variant="outlined"
@@ -496,10 +526,10 @@ const SettingsScheduler: FC = () => {
startIcon={<DoneIcon />} startIcon={<DoneIcon />}
variant="outlined" variant="outlined"
type="submit" type="submit"
onClick={() => updateScheduleItem()} onClick={() => validateScheduleItem()}
color="primary" color="primary"
> >
{LL.UPDATE()} {creating ? LL.ADD(0) : LL.UPDATE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -534,6 +564,13 @@ const SettingsScheduler: FC = () => {
</ButtonRow> </ButtonRow>
)} )}
</Box> </Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button startIcon={<AddIcon />} variant="outlined" color="secondary" onClick={() => addScheduleItem()}>
{LL.ADD(0)}
</Button>
</ButtonRow>
</Box>
</Box> </Box>
</SectionContent> </SectionContent>
); );

View File

@@ -1,6 +1,6 @@
import Schema, { InternalRuleItem } from 'async-validator'; import Schema, { InternalRuleItem } from 'async-validator';
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
import { Settings } from './types'; import { Settings, ScheduleItem } from './types';
export const GPIO_VALIDATOR = { export const GPIO_VALIDATOR = {
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
@@ -84,3 +84,8 @@ export const createSettingsValidator = (settings: Settings) =>
shower_alert_coldshot: [{ type: 'number', min: 1, max: 10, message: 'Time must be between 1 and 10 seconds' }] shower_alert_coldshot: [{ type: 'number', min: 1, max: 10, message: 'Time must be between 1 and 10 seconds' }]
}) })
}); });
export const schedulerItemValidation = (si: ScheduleItem, creating: boolean) =>
new Schema({
cmd: [{ required: true, message: 'Command is required' }]
});