import { useCallback, useEffect, useMemo, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid, InputAdornment, MenuItem, Typography } from '@mui/material'; import { dialogStyle } from 'CustomTheme'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; import { ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { numberValue, updateValue } from 'utils'; import { validate } from 'validators'; import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types'; import type { AnalogSensor } from './types'; interface DashboardSensorsAnalogDialogProps { open: boolean; onClose: () => void; onSave: (as: AnalogSensor) => void; creating: boolean; selectedItem: AnalogSensor; analogGPIOList: number[]; validator: Schema; } const SensorsAnalogDialog = ({ open, onClose, onSave, creating, selectedItem, analogGPIOList, validator }: DashboardSensorsAnalogDialogProps) => { const { LL } = useI18nContext(); const [fieldErrors, setFieldErrors] = useState(); const [editItem, setEditItem] = useState(selectedItem); const updateFormValue = useMemo( () => updateValue((updater) => setEditItem( (prev) => updater( prev as unknown as Record ) as unknown as AnalogSensor ) ), [setEditItem] ); // Memoize helper functions to check sensor type conditions const isCounterOrRate = useMemo( () => editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE, [editItem.t] ); const isFreqType = useMemo( () => editItem.t >= AnalogType.FREQ_0 && editItem.t <= AnalogType.FREQ_2, [editItem.t] ); const isPWM = useMemo( () => editItem.t === AnalogType.PWM_0 || editItem.t === AnalogType.PWM_1 || editItem.t === AnalogType.PWM_2, [editItem.t] ); const isDigitalOutGPIO = useMemo( () => editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26), [editItem.t, editItem.g] ); const isDigitalOutNonGPIO = useMemo( () => editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26, [editItem.t, editItem.g] ); // Memoize menu items to avoid recreation on each render const analogTypeMenuItems = useMemo( () => AnalogTypeNames.map((val, i) => ( {val} )), [] ); const uomMenuItems = useMemo( () => DeviceValueUOM_s.map((val, i) => ( {val} )), [] ); const analogGPIOMenuItems = () => // add selectedItem.g to the list [ ...(analogGPIOList?.includes(selectedItem.g) || selectedItem.g === undefined ? analogGPIOList : [selectedItem.g, ...analogGPIOList]) ] .filter((gpio, idx, arr) => arr.indexOf(gpio) === idx) .sort((a, b) => a - b) .map((gpio: number) => { return ( {gpio} ); }); // Reset form when dialog opens or selectedItem changes useEffect(() => { if (open) { setFieldErrors(undefined); setEditItem(selectedItem); } }, [open, selectedItem]); const handleClose = useCallback( (_event: React.SyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown') => { if (reason !== 'backdropClick') { onClose(); } }, [onClose] ); const save = useCallback(async () => { try { setFieldErrors(undefined); await validate(validator, editItem); onSave(editItem); } catch (error) { setFieldErrors(error as ValidateFieldsError); } }, [validator, editItem, onSave]); const remove = useCallback(() => { onSave({ ...editItem, d: true }); }, [editItem, onSave]); const dialogTitle = useMemo( () => `${creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()} ${LL.ANALOG_SENSOR(0)}`, [creating, LL] ); return ( {dialogTitle} {analogGPIOMenuItems()} {analogTypeMenuItems} {(isCounterOrRate || isFreqType) && ( {uomMenuItems} )} {editItem.t === AnalogType.ADC && ( mV ) }, htmlInput: { min: '0', max: '3300', step: '1' } }} /> )} {editItem.t === AnalogType.NTC && ( °C ) }, htmlInput: { min: '-20', max: '20', step: '0.1' } }} /> )} {editItem.t === AnalogType.COUNTER && ( )} {editItem.t === AnalogType.RGB && ( )} {isCounterOrRate && ( )} {isDigitalOutGPIO && ( )} {isDigitalOutNonGPIO && ( <> {LL.OFF()} {LL.ON()} {LL.ACTIVEHIGH()} {LL.ACTIVELOW()} {LL.UNCHANGED()} {LL.ALWAYS()} {LL.OFF()} {LL.ALWAYS()} {LL.ON()} )} {isPWM && ( <> Hz ) }, htmlInput: { min: '1', max: '5000', step: '1' } }} /> % ) }, htmlInput: { min: '0', max: '100', step: '0.1' } }} /> )} {editItem.t === AnalogType.PULSE && ( <> {LL.ACTIVEHIGH()} {LL.ACTIVELOW()} s ) }, htmlInput: { min: '0', max: '10000', step: '0.1' } }} /> )} {fieldErrors && Object.keys(fieldErrors).length > 0 && ( {Object.values(fieldErrors).map((errArr, idx) => Array.isArray(errArr) ? errArr.map((err, j) => ( {err.message} )) : null )} )} {editItem.s && ( {LL.SYSTEM(0)} {LL.SENSOR(0)} )} {!creating && ( )} ); }; export default SensorsAnalogDialog;