import { useCallback, useEffect, useMemo, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import DoneIcon from '@mui/icons-material/Done'; import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined'; import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, Grid, InputAdornment, MenuItem, TextField } from '@mui/material'; import { dialogStyle } from 'CustomTheme'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { numberValue, updateValue } from 'utils'; import { validate } from 'validators'; import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types'; import type { EntityItem } from './types'; // Constant value type options for the dropdown const VALUE_TYPE_OPTIONS = [ DeviceValueType.BOOL, DeviceValueType.INT8, DeviceValueType.UINT8, DeviceValueType.INT16, DeviceValueType.UINT16, DeviceValueType.UINT24, DeviceValueType.TIME, DeviceValueType.UINT32, DeviceValueType.STRING ] as const; interface CustomEntitiesDialogProps { open: boolean; creating: boolean; onClose: () => void; onSave: (ei: EntityItem) => void; onDup: (ei: EntityItem) => void; selectedItem: EntityItem; validator: Schema; } const CustomEntitiesDialog = ({ open, creating, onClose, onSave, onDup, selectedItem, validator }: CustomEntitiesDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); const updateFormValue = useMemo( () => updateValue( setEditItem as unknown as React.Dispatch< React.SetStateAction> > ), [] ); useEffect(() => { if (open) { setFieldErrors(undefined); // Convert to hex strings - combined into single setEditItem call const deviceIdHex = typeof selectedItem.device_id === 'number' ? selectedItem.device_id.toString(16).toUpperCase() : selectedItem.device_id; const typeIdHex = typeof selectedItem.type_id === 'number' ? selectedItem.type_id.toString(16).toUpperCase() : selectedItem.type_id; const factorValue = selectedItem.value_type === DeviceValueType.BOOL && typeof selectedItem.factor === 'number' ? selectedItem.factor.toString(16).toUpperCase() : selectedItem.factor; setEditItem({ ...selectedItem, device_id: deviceIdHex, type_id: typeIdHex, factor: factorValue }); } }, [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); // Create a copy to avoid mutating the state directly const processedItem: EntityItem = { ...editItem }; if (typeof processedItem.device_id === 'string') { processedItem.device_id = Number.parseInt(processedItem.device_id, 16); } if (typeof processedItem.type_id === 'string') { processedItem.type_id = Number.parseInt(processedItem.type_id, 16); } if ( processedItem.value_type === DeviceValueType.BOOL && typeof processedItem.factor === 'string' ) { processedItem.factor = Number.parseInt(processedItem.factor, 16); } onSave(processedItem); } catch (error) { setFieldErrors(error as ValidateFieldsError); } }, [validator, editItem, onSave]); const remove = useCallback(() => { const itemWithDeleted = { ...editItem, deleted: true }; onSave(itemWithDeleted); }, [editItem, onSave]); const dup = useCallback(() => { onDup(editItem); }, [editItem, onDup]); // Memoize UOM menu items to avoid recreating on every render const uomMenuItems = useMemo( () => DeviceValueUOM_s.map((val, i) => ( {val} )), [] ); return ( {creating ? LL.ADD(1) + ' ' + LL.NEW(1) : LL.EDIT()} {LL.ENTITY()} } checkedIcon={} checked={editItem.hide} onChange={updateFormValue} name="hide" /> } label="API/MQTT" /> EMS-{LL.VALUE(1)} RAM-{LL.VALUE(1)} {editItem.ram === 1 && ( <> {uomMenuItems} )} {editItem.ram === 0 && ( <> } checkedIcon={} checked={editItem.writeable} onChange={updateFormValue} name="writeable" /> } label={LL.WRITEABLE()} /> 0x ) }, htmlInput: { style: { textTransform: 'uppercase' } } }} /> 0x ) }, htmlInput: { style: { textTransform: 'uppercase' } } }} /> {VALUE_TYPE_OPTIONS.map((valueType) => ( {DeviceValueTypeNames[valueType]} ))} {editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && ( <> {uomMenuItems} )} {editItem.value_type === DeviceValueType.STRING && editItem.device_id !== '0' && ( )} {editItem.value_type === DeviceValueType.BOOL && ( 0x ) }, htmlInput: { style: { textTransform: 'uppercase' } } }} /> )} )} {!creating && ( )} ); }; export default CustomEntitiesDialog;