From f80764d72b988fe31579b1e8dd16d435f6a0a541 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 22 Apr 2023 09:44:24 +0200 Subject: [PATCH] refactor diallogs --- interface/src/project/SettingsEntities.tsx | 100 ++-- .../src/project/SettingsEntitiesDialog.tsx | 66 +-- interface/src/project/SettingsScheduler.tsx | 465 ++++-------------- .../src/project/SettingsSchedulerDialog.tsx | 254 ++++++++++ interface/src/project/types.ts | 12 +- interface/src/project/validators.ts | 56 ++- 6 files changed, 462 insertions(+), 491 deletions(-) create mode 100644 interface/src/project/SettingsSchedulerDialog.tsx diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 08163a86e..d1d2d6504 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -1,36 +1,30 @@ -import type { FC } from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import WarningIcon from '@mui/icons-material/Warning'; +import { Button, Typography, Box } from '@mui/material'; +import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; +import { useTheme } from '@table-library/react-table-library/theme'; import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; -import { Button, Typography, Box } from '@mui/material'; - -import { useTheme } from '@table-library/react-table-library/theme'; -import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; - import { toast } from 'react-toastify'; -import WarningIcon from '@mui/icons-material/Warning'; -import CancelIcon from '@mui/icons-material/Cancel'; -import AddIcon from '@mui/icons-material/Add'; - +import SettingsEntitiesDialog from './SettingsEntitiesDialog'; +import * as EMSESP from './api'; +import { DeviceValueUOM_s } from './types'; +import { entityItemValidation } from './validators'; +import type { EntityItem } from './types'; +import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; -import SettingsEntitiesDialog from './SettingsEntitiesDialog'; - -import type { EntityItem } from './types'; -import { DeviceValueUOM_s } from './types'; -import { extractErrorMessage } from 'utils'; - import { useI18nContext } from 'i18n/i18n-react'; - -import * as EMSESP from './api'; -import { entityItemValidation } from './validators'; +import { extractErrorMessage } from 'utils'; const SettingsEntities: FC = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [entities, setEntities] = useState([]); + const [entities, setEntities] = useState(); const [selectedEntityItem, setSelectedEntityItem] = useState(); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); @@ -52,7 +46,9 @@ const SettingsEntities: FC = () => { } useEffect(() => { - setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); + if (entities) { + setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); + } }, [entities]); const entity_theme = useTheme({ @@ -109,28 +105,24 @@ const SettingsEntities: FC = () => { ` }); - const setOriginalEntity = (data: EntityItem[]) => { - setEntities( - data.map((ei) => ({ - ...ei, - o_id: ei.id, - o_device_id: ei.device_id, - o_type_id: ei.type_id, - o_offset: ei.offset, - o_factor: ei.factor, - o_uom: ei.uom, - o_value_type: ei.value_type, - o_name: ei.name, - o_writeable: ei.writeable, - o_deleted: ei.deleted - })) - ); - }; - const fetchEntities = useCallback(async () => { try { const response = await EMSESP.readEntities(); - setOriginalEntity(response.data.entities); + setEntities( + response.data.entities.map((ei) => ({ + ...ei, + o_id: ei.id, + o_device_id: ei.device_id, + o_type_id: ei.type_id, + o_offset: ei.offset, + o_factor: ei.factor, + o_uom: ei.uom, + o_value_type: ei.value_type, + o_name: ei.name, + o_writeable: ei.writeable, + o_deleted: ei.deleted + })) + ); } catch (error) { setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } @@ -160,14 +152,14 @@ const SettingsEntities: FC = () => { }); if (response.status === 200) { - toast.success(LL.SUCCESS()); + toast.success(LL.ENTITIES_UPDATED()); } else { toast.error(LL.PROBLEM_UPDATING()); } + void fetchEntities(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } - setOriginalEntity(entities); } }; @@ -182,26 +174,25 @@ const SettingsEntities: FC = () => { }; const onDialogSave = (updatedItem: EntityItem) => { - if (creating) { + setDialogOpen(false); + if (entities && creating) { setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]); } else { - setEntities(entities.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); + setEntities(entities?.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); } - setDialogOpen(false); }; - // TODO need callback here too? const addEntityItem = () => { setCreating(true); setSelectedEntityItem({ id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), + name: '', device_id: 11, type_id: 0, offset: 0, factor: 1, - value_type: 2, uom: 0, - name: '', + value_type: 2, writeable: false, deleted: false }); @@ -234,8 +225,8 @@ const SettingsEntities: FC = () => {
{LL.NAME(0)} - Device ID - Type ID + {LL.ID_OF(LL.DEVICE())} + {LL.ID_OF(LL.TYPE(1))} Offset {LL.VALUE(0)} @@ -244,8 +235,8 @@ const SettingsEntities: FC = () => { {tableList.map((ei: EntityItem) => ( editEntityItem(ei)}> {ei.name} - {showHex(ei.device_id, 2)} - {showHex(ei.type_id, 4)} + {showHex(ei.device_id as number, 2)} + {showHex(ei.type_id as number, 4)} {ei.offset} {formatValue(ei.value, ei.uom)} @@ -258,11 +249,12 @@ const SettingsEntities: FC = () => { }; return ( - + {blocker ? : null} {LL.ENTITIES_HELP_1()} + {renderEntity()} {selectedEntityItem && ( @@ -272,7 +264,7 @@ const SettingsEntities: FC = () => { onClose={onDialogClose} onSave={onDialogSave} selectedEntityItem={selectedEntityItem} - validator={entityItemValidation(entities, creating)} + validator={entityItemValidation()} /> )} diff --git a/interface/src/project/SettingsEntitiesDialog.tsx b/interface/src/project/SettingsEntitiesDialog.tsx index 4ac93ff76..8fae8dfc8 100644 --- a/interface/src/project/SettingsEntitiesDialog.tsx +++ b/interface/src/project/SettingsEntitiesDialog.tsx @@ -1,35 +1,32 @@ -import { useState, useEffect } from 'react'; - +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import DoneIcon from '@mui/icons-material/Done'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import { - Grid, - Button, Box, + Button, + Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, - Checkbox, + Grid, InputAdornment, MenuItem } from '@mui/material'; +import { useEffect, useState } from 'react'; -import { ValidatedTextField, BlockFormControlLabel } from 'components'; +import { DeviceValueUOM_s } from './types'; +import type { EntityItem } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; -import DoneIcon from '@mui/icons-material/Done'; -import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; -import CancelIcon from '@mui/icons-material/Cancel'; -import AddIcon from '@mui/icons-material/Add'; +import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { validate } from 'validators'; -import type { ValidateFieldsError } from 'async-validator'; -import type Schema from 'async-validator'; - import { updateValue } from 'utils'; - -import type { EntityItem } from './types'; -import { DeviceValueUOM_s } from './types'; +import { validate } from 'validators'; type SettingsEntitiesDialogProps = { open: boolean; @@ -58,6 +55,12 @@ const SettingsEntitiesDialog = ({ if (open) { setFieldErrors(undefined); setEditItem(selectedEntityItem); + // convert to hex strings straight away + setEditItem({ + ...selectedEntityItem, + device_id: ('0' + selectedEntityItem.device_id.toString(16).toUpperCase()).slice(-2), + type_id: ('000' + selectedEntityItem.type_id.toString(16).toUpperCase()).slice(-4) + }); } }, [open, selectedEntityItem]); @@ -69,6 +72,12 @@ const SettingsEntitiesDialog = ({ try { setFieldErrors(undefined); await validate(validator, editItem); + if (typeof editItem.device_id === 'string') { + editItem.device_id = parseInt(editItem.device_id, 16); + } + if (typeof editItem.type_id === 'string') { + editItem.type_id = parseInt(editItem.type_id, 16); + } onSave(editItem); } catch (errors: any) { setFieldErrors(errors); @@ -83,13 +92,12 @@ const SettingsEntitiesDialog = ({ return ( - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.ENTITY()} + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} - - } + control={} label={LL.WRITEABLE()} /> @@ -113,28 +120,26 @@ const SettingsEntitiesDialog = ({ 0x - }} + inputProps={{ style: { textTransform: 'uppercase' } }} + InputProps={{ startAdornment: 0x }} /> 0x - }} + inputProps={{ style: { textTransform: 'uppercase' } }} + InputProps={{ startAdornment: 0x }} /> @@ -169,7 +174,8 @@ const SettingsEntitiesDialog = ({ TIME - {selectedEntityItem.value_type !== 0 && ( + + {editItem.value_type !== 0 && ( <> { const { LL, locale } = useI18nContext(); - const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - - const emptySchedule = { - id: 0, - active: false, - deleted: false, - flags: 0, - time: '12:00', - cmd: '', - value: '', - name: '', - o_name: '' - }; - - const [schedule, setSchedule] = useState([emptySchedule]); - const [scheduleItem, setScheduleItem] = useState(); + const [schedule, setSchedule] = useState([]); + const [selectedScheduleItem, setSelectedScheduleItem] = useState(); const [dow, setDow] = useState([]); const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); - const [fieldErrors, setFieldErrors] = useState(); - - // eslint-disable-next-line - const [flags, setFlags] = useState(() => ['']); - - function getDayNames() { - const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); - const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { - const dd = day < 10 ? `0${day}` : day; - return new Date(`2017-01-${dd}T00:00:00+00:00`); - }); - return days.map((date) => formatter.format(date)); - } + const [dialogOpen, setDialogOpen] = useState(false); function hasScheduleChanged(si: ScheduleItem) { return ( @@ -107,17 +45,11 @@ const SettingsScheduler: FC = () => { ); } - // TODO fix - const getNumChanges = () => { - if (!schedule) { - return 0; - } - return schedule.filter((si) => hasScheduleChanged(si)).length; - }; - useEffect(() => { - setNumChanges(getNumChanges()); - }); + if (schedule) { + setNumChanges(schedule ? schedule.filter((si) => hasScheduleChanged(si)).length : 0); + } + }, [schedule]); const schedule_theme = useTheme({ Table: ` @@ -164,72 +96,37 @@ const SettingsScheduler: FC = () => { ` }); - const setOriginalSchedule = (data: ScheduleItem[]) => { - setSchedule( - data.map((si) => ({ - ...si, - o_id: si.id, - o_active: si.active, - o_deleted: si.deleted, - o_flags: si.flags, - o_time: si.time, - o_cmd: si.cmd, - o_value: si.value, - o_name: si.name - })) - ); - }; - const fetchSchedule = useCallback(async () => { try { const response = await EMSESP.readSchedule(); - setOriginalSchedule(response.data.schedule); + setSchedule( + response.data.schedule.map((si) => ({ + ...si, + o_id: si.id, + o_active: si.active, + o_deleted: si.deleted, + o_flags: si.flags, + o_time: si.time, + o_cmd: si.cmd, + o_value: si.value, + o_name: si.name + })) + ); } catch (error) { setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); } }, [LL]); + // on mount useEffect(() => { + const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); + const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { + const dd = day < 10 ? `0${day}` : day; + return new Date(`2017-01-${dd}T00:00:00+00:00`); + }); + setDow(days.map((date) => formatter.format(date))); void fetchSchedule(); - setDow(getDayNames()); - }, [getDayNames, fetchSchedule]); - - const getFlagNumber = (newFlag: string[]) => { - let new_flag = 0; - for (const entry of newFlag) { - new_flag |= Number(entry); - } - return new_flag; - }; - - const getFlagString = (f: number) => { - const new_flags: string[] = []; - if ((f & 1) === 1) { - new_flags.push('1'); - } - if ((f & 2) === 2) { - new_flags.push('2'); - } - if ((f & 4) === 4) { - new_flags.push('4'); - } - if ((f & 8) === 8) { - new_flags.push('8'); - } - if ((f & 16) === 16) { - new_flags.push('16'); - } - if ((f & 32) === 32) { - new_flags.push('32'); - } - if ((f & 64) === 64) { - new_flags.push('64'); - } - if ((f & 128) === 128) { - new_flags.push('128'); - } - return new_flags; - }; + }, [locale, fetchSchedule]); const saveSchedule = async () => { if (schedule) { @@ -248,74 +145,40 @@ const SettingsScheduler: FC = () => { })) }); if (response.status === 200) { - toast.success(LL.SCHEDULE_SAVED()); + toast.success(LL.SCHEDULE_UPDATED()); } else { toast.error(LL.PROBLEM_UPDATING()); } + void fetchSchedule(); } catch (error) { toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); } - setOriginalSchedule(schedule); } }; - function getFlagName(flag: number) { - if ((flag & ScheduleFlag.SCHEDULE_MON) === ScheduleFlag.SCHEDULE_MON) { - return dow[1]; - } - if ((flag & ScheduleFlag.SCHEDULE_TUE) === ScheduleFlag.SCHEDULE_TUE) { - return dow[2]; - } - if ((flag & ScheduleFlag.SCHEDULE_WED) === ScheduleFlag.SCHEDULE_WED) { - return dow[3]; - } - if ((flag & ScheduleFlag.SCHEDULE_THU) === ScheduleFlag.SCHEDULE_THU) { - return dow[4]; - } - if ((flag & ScheduleFlag.SCHEDULE_FRI) === ScheduleFlag.SCHEDULE_FRI) { - return dow[5]; - } - if ((flag & ScheduleFlag.SCHEDULE_SAT) === ScheduleFlag.SCHEDULE_SAT) { - return dow[6]; - } - if ((flag & ScheduleFlag.SCHEDULE_SUN) === ScheduleFlag.SCHEDULE_SUN) { - return dow[0]; - } - if ((flag & ScheduleFlag.SCHEDULE_TIMER) === ScheduleFlag.SCHEDULE_TIMER) { - return LL.TIMER(0); - } - return ''; - } - - const dayBox = (si: ScheduleItem, flag: number) => ( - <> - - - {getFlagName(flag)} - - - - - ); - - const showFlag = (si: ScheduleItem, flag: number) => ( - - {getFlagName(flag)} - - ); - - const editScheduleItem = (si: ScheduleItem) => { - if (si.name === undefined) { - si.name = ''; - } + const editScheduleItem = useCallback((si: ScheduleItem) => { setCreating(false); - setScheduleItem(si); + setSelectedScheduleItem(si); + setDialogOpen(true); + }, []); + + const onDialogClose = () => { + setDialogOpen(false); + }; + + const onDialogSave = (updatedItem: ScheduleItem) => { + setDialogOpen(false); + if (schedule && creating) { + setSchedule([...schedule.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]); + } else { + setSchedule(schedule?.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si))); + } }; const addScheduleItem = () => { setCreating(true); - setScheduleItem({ - id: makeid(), + setSelectedScheduleItem({ + id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100), active: false, deleted: false, flags: 0, @@ -324,13 +187,7 @@ const SettingsScheduler: FC = () => { value: '', name: '' }); - }; - - const updateScheduleItem = () => { - if (scheduleItem) { - setSchedule([...schedule.filter((si) => creating || si.o_id !== scheduleItem.o_id), scheduleItem]); - } - setScheduleItem(undefined); + setDialogOpen(true); }; const renderSchedule = () => { @@ -338,6 +195,17 @@ const SettingsScheduler: FC = () => { return ; } + const dayBox = (si: ScheduleItem, flag: number) => ( + <> + + + {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + + + + ); + return ( !si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }} @@ -388,173 +256,6 @@ const SettingsScheduler: FC = () => { ); }; - const removeScheduleItem = (si: ScheduleItem) => { - si.deleted = true; - setScheduleItem(si); - updateScheduleItem(); - }; - - const validateScheduleItem = async () => { - if (scheduleItem) { - try { - setFieldErrors(undefined); - await validate(schedulerItemValidation(schedule, scheduleItem), scheduleItem); - updateScheduleItem(); - } catch (errors: any) { - setFieldErrors(errors); - } - } - }; - - const closeDialog = () => { - setScheduleItem(undefined); - setFieldErrors(undefined); - }; - - const renderEditSchedule = () => { - if (scheduleItem) { - const isTimer = scheduleItem.flags === ScheduleFlag.SCHEDULE_TIMER; - return ( - closeDialog()}> - - {creating ? LL.ADD(1) + ' ' + LL.NEW() : LL.EDIT()} {LL.SCHEDULE(1)} - - - - - { - scheduleItem.flags = getFlagNumber(flag) & 127; - setFlags(['']); // forces refresh - }} - > - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_MON)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_TUE)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_WED)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_THU)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_FRI)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_SAT)} - {showFlag(scheduleItem, ScheduleFlag.SCHEDULE_SUN)} - - - - {isTimer ? ( - - ) : ( - - )} - - - - - } - label={LL.ACTIVE()} - /> - {scheduleItem.active && ( - - - - )} - - - - - {isTimer && ( - - {LL.SCHEDULER_HELP_2()} - - )} - - - - - - - {!creating && ( - - - - )} - - - - - ); - } - }; - return ( {blocker ? : null} @@ -562,7 +263,19 @@ const SettingsScheduler: FC = () => { {LL.SCHEDULER_HELP_1()} {renderSchedule()} - {renderEditSchedule()} + + {selectedScheduleItem && ( + + )} + {numChanges !== 0 && ( diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx new file mode 100644 index 000000000..976b7b2d5 --- /dev/null +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -0,0 +1,254 @@ +import AddIcon from '@mui/icons-material/Add'; +import CancelIcon from '@mui/icons-material/Cancel'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import DoneIcon from '@mui/icons-material/Done'; +import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; + +import { + Box, + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Grid, + TextField, + ToggleButton, + ToggleButtonGroup, + Typography +} from '@mui/material'; +import { useEffect, useState } from 'react'; + +import { ScheduleFlag } from './types'; +import type { ScheduleItem } from './types'; +import type Schema from 'async-validator'; +import type { ValidateFieldsError } from 'async-validator'; + +import { BlockFormControlLabel, ValidatedTextField } from 'components'; + +import { useI18nContext } from 'i18n/i18n-react'; + +import { updateValue } from 'utils'; +import { validate } from 'validators'; + +type SettingsSchedulerDialogProps = { + open: boolean; + creating: boolean; + onClose: () => void; + onSave: (ei: ScheduleItem) => void; + selectedSchedulerItem: ScheduleItem; + validator: Schema; + dow: string[]; +}; + +const SettingsSchedulerDialog = ({ + open, + creating, + onClose, + onSave, + selectedSchedulerItem, + validator, + dow +}: SettingsSchedulerDialogProps) => { + const { LL } = useI18nContext(); + const [editItem, setEditItem] = useState(selectedSchedulerItem); + const [fieldErrors, setFieldErrors] = useState(); + + const updateFormValue = updateValue(setEditItem); + + useEffect(() => { + if (open) { + setFieldErrors(undefined); + setEditItem(selectedSchedulerItem); + } + }, [open, selectedSchedulerItem]); + + const close = () => { + onClose(); + }; + + const save = async () => { + try { + setFieldErrors(undefined); + await validate(validator, editItem); + onSave(editItem); + } catch (errors: any) { + setFieldErrors(errors); + } + }; + + const remove = () => { + editItem.deleted = true; + onSave(editItem); + }; + + const getFlagNumber = (newFlag: string[]) => { + let new_flag = 0; + for (const entry of newFlag) { + new_flag |= Number(entry); + } + return new_flag; + }; + + const getFlagString = (f: number) => { + const new_flags: string[] = []; + if ((f & 1) === 1) { + new_flags.push('1'); + } + if ((f & 2) === 2) { + new_flags.push('2'); + } + if ((f & 4) === 4) { + new_flags.push('4'); + } + if ((f & 8) === 8) { + new_flags.push('8'); + } + if ((f & 16) === 16) { + new_flags.push('16'); + } + if ((f & 32) === 32) { + new_flags.push('32'); + } + if ((f & 64) === 64) { + new_flags.push('64'); + } + if ((f & 128) === 128) { + new_flags.push('128'); + } + return new_flags; + }; + + const showFlag = (si: ScheduleItem, flag: number) => ( + + {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + ); + + const isTimer = editItem.flags === ScheduleFlag.SCHEDULE_TIMER; + + return ( + + + {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} + + + + + { + setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 }); + }} + > + {showFlag(editItem, ScheduleFlag.SCHEDULE_MON)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_TUE)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_WED)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_THU)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_FRI)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_SAT)} + {showFlag(editItem, ScheduleFlag.SCHEDULE_SUN)} + + + + {isTimer ? ( + + ) : ( + + )} + + + + } + label={LL.ACTIVE()} + /> + {editItem.active && ( + + + + )} + + + + {isTimer && ( + + {LL.SCHEDULER_HELP_2()} + + )} + + + + + + + {!creating && ( + + + + )} + + + + + ); +}; + +export default SettingsSchedulerDialog; diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 2fa83fcab..c0179b680 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -60,7 +60,7 @@ export interface Status { } export interface Device { - id: string; // id index + id: number; // id index tn: string; // device type translated name t: number; // device type id b: string; // brand @@ -341,19 +341,19 @@ export enum ScheduleFlag { export interface EntityItem { id: number; // unique number name: string; - device_id: number; - type_id: number; + device_id: number | string; + type_id: number | string; offset: number; factor: number; uom: number; value_type: number; - value?: number; + value?: number; // optional writeable: boolean; deleted?: boolean; // optional o_id?: number; o_name?: string; - o_device_id?: number; - o_type_id?: number; + o_device_id?: number | string; + o_type_id?: number | string; o_offset?: number; o_factor?: number; o_uom?: number; diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 3d7e9b795..e394a10f4 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -1,7 +1,7 @@ -import type { InternalRuleItem } from 'async-validator'; import Schema from 'async-validator'; +import type { Settings } from './types'; +import type { InternalRuleItem } from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; -import type { Settings, ScheduleItem, EntityItem } from './types'; export const GPIO_VALIDATOR = { validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { @@ -86,25 +86,14 @@ export const createSettingsValidator = (settings: Settings) => }) }); -export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({ - validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) { - if (name && o_name && o_name !== name && schedule.find((si) => si.name === name)) { - callback('Name already in use'); - } else { - callback(); - } - } -}); - -export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) => +export const schedulerItemValidation = () => new Schema({ name: [ { type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" - }, - ...[uniqueNameValidator(schedule, scheduleItem.o_name)] + } ], cmd: [ { required: true, message: 'Command is required' }, @@ -112,7 +101,7 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ] }); -export const entityItemValidation = (entities: EntityItem[], creating: boolean) => +export const entityItemValidation = () => new Schema({ name: [ { required: true, message: 'Name is required' }, @@ -122,13 +111,30 @@ export const entityItemValidation = (entities: EntityItem[], creating: boolean) message: "Must be <15 characters: alpha numeric, '_' or '.'" } ], - device_id: [{ type: 'hex', required: true, message: 'ID must be a hex value' }] - // type_id: [ - // { required: true, message: 'Type_id is required' }, - // { type: 'string', pattern: /^[A-F0-9]{1,4}$/, message: 'Must be a hex number' } - // ], - // offset: [ - // { required: true, message: 'Offset is required' }, - // { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } - // ] + device_id: [ + { required: true, message: 'Device ID is required' }, + { + validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + if (isNaN(parseInt(value, 16))) { + callback('Must be a hex number'); + } + callback(); + } + } + ], + type_id: [ + { required: true, message: 'Type ID is required' }, + { + validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + if (isNaN(parseInt(value, 16))) { + callback('Must be a hex number'); + } + callback(); + } + } + ], + offset: [ + { required: true, message: 'Offset is required' }, + { type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' } + ] });