import { FC, useState, useEffect, useCallback } from 'react'; import { Button, Typography, Box, MenuItem, Dialog, DialogActions, DialogContent, DialogTitle, ToggleButton, ToggleButtonGroup, Tooltip, Grid, TextField } from '@mui/material'; import { Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useSnackbar } from 'notistack'; import SaveIcon from '@mui/icons-material/Save'; import CancelIcon from '@mui/icons-material/Cancel'; import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined'; import StarIcon from '@mui/icons-material/Star'; import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'; import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined'; import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import SearchIcon from '@mui/icons-material/Search'; import FilterListIcon from '@mui/icons-material/FilterList'; import { ButtonRow, FormLoader, ValidatedTextField, SectionContent } from '../components'; import * as EMSESP from './api'; import { extractErrorMessage } from '../utils'; import { DeviceShort, Devices, DeviceEntity, DeviceEntityMask } from './types'; const SettingsCustomization: FC = () => { const { enqueueSnackbar } = useSnackbar(); const [deviceEntities, setDeviceEntities] = useState([{ id: '', v: 0, s: '', m: 0, w: false }]); const [devices, setDevices] = useState(); const [errorMessage, setErrorMessage] = useState(); const [selectedDevice, setSelectedDevice] = useState(0); const [confirmReset, setConfirmReset] = useState(false); const [selectedFilters, setSelectedFilters] = useState(0); const [search, setSearch] = useState(''); // eslint-disable-next-line const [masks, setMasks] = useState(() => ['']); const entities_theme = useTheme({ BaseRow: ` font-size: 14px; color: white; min-height: 32px; `, HeaderRow: ` text-transform: uppercase; background-color: black; border-bottom: 1px solid #e0e0e0; color: #90CAF9; font-weight: 500; `, Row: ` background-color: #1e1e1e; border-top: 1px solid #565656; border-bottom: 1px solid #565656; position: relative; z-index: 1; &:not(:last-of-type) { margin-bottom: -1px; } &:not(:first-of-type) { margin-top: -1px; } &:hover { z-index: 2; color: white; border-top: 1px solid #177ac9; border-bottom: 1px solid #177ac9; }, &.tr.tr-body.row-select.row-select-single-selected, &.tr.tr-body.row-select.row-select-selected { background-color: #3d4752; color: white; font-weight: normal; z-index: 2; border-top: 1px solid #177ac9; border-bottom: 1px solid #177ac9; } `, BaseCell: ` padding-left: 8px; border-top: 1px solid transparent; border-right: 1px solid transparent; border-bottom: 1px solid transparent; &:nth-of-type(1) { left: 0px; min-width: 124px; width: 124px; padding-left: 0px; } &:nth-of-type(2) { min-width: 70%; width: 70%; } `, HeaderCell: ` padding-left: 0px; &:nth-of-type(1) { padding-left: 24px; } &:nth-of-type(3) { border-left: 1px solid #565656; } ` }); const getSortIcon = (state: any, sortKey: any) => { if (state.sortKey === sortKey && state.reverse) { return ; } if (state.sortKey === sortKey && !state.reverse) { return ; } return ; }; const entity_sort = useSort( { nodes: deviceEntities }, {}, { sortIcon: { iconDefault: , iconUp: , iconDown: }, sortToggleType: SortToggleType.AlternateWithReset, sortFns: { NAME: (array) => array.sort((a, b) => a.id.localeCompare(b.id)) } } ); const fetchDevices = useCallback(async () => { try { setDevices((await EMSESP.readDevices()).data); } catch (error: any) { setErrorMessage(extractErrorMessage(error, 'Failed to fetch device list')); } }, []); const setInitialMask = (data: DeviceEntity[]) => { setDeviceEntities(data.map((de) => ({ ...de, om: de.m }))); }; const fetchDeviceEntities = async (unique_id: number) => { try { const data = (await EMSESP.readDeviceEntities({ id: unique_id })).data; setInitialMask(data); } catch (error: any) { setErrorMessage(extractErrorMessage(error, 'Problem fetching device entities')); } }; useEffect(() => { fetchDevices(); }, [fetchDevices]); function formatValue(value: any) { if (typeof value === 'number') { return new Intl.NumberFormat().format(value); } else if (value === undefined) { return ''; } else if (typeof value === 'boolean') { return value ? 'true' : 'false'; } return value; } const getMaskNumber = (newMask: string[]) => { var new_mask = 0; for (let entry of newMask) { new_mask |= Number(entry); } return new_mask; }; const getMaskString = (m: number) => { var new_masks = []; if ((m & 1) === 1) { new_masks.push('1'); } if ((m & 2) === 2) { new_masks.push('2'); } if ((m & 4) === 4) { new_masks.push('4'); } if ((m & 8) === 8) { new_masks.push('8'); } return new_masks; }; const maskDisabled = (set: boolean) => { setDeviceEntities( deviceEntities.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { ...de, m: set ? de.m | (DeviceEntityMask.DV_API_MQTT_EXCLUDE | DeviceEntityMask.DV_WEB_EXCLUDE) : de.m & ~(DeviceEntityMask.DV_API_MQTT_EXCLUDE | DeviceEntityMask.DV_WEB_EXCLUDE) }; } else { return de; } }) ); }; function compareDevices(a: DeviceShort, b: DeviceShort) { if (a.s < b.s) { return -1; } if (a.s > b.s) { return 1; } return 0; } const changeSelectedDevice = (event: React.ChangeEvent) => { const selected_device = parseInt(event.target.value, 10); setSelectedDevice(selected_device); fetchDeviceEntities(selected_device); }; const resetCustomization = async () => { try { await EMSESP.resetCustomizations(); enqueueSnackbar('All customizations have been removed. Restarting...', { variant: 'info' }); } catch (error: any) { enqueueSnackbar(extractErrorMessage(error, 'Problem resetting customizations'), { variant: 'error' }); } finally { setConfirmReset(false); } }; const saveCustomization = async () => { if (deviceEntities && selectedDevice) { const masked_entities = deviceEntities .filter((de) => de.m !== de.om) .map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.s); if (masked_entities.length > 60) { enqueueSnackbar('Selected entities exceeded limit of 60. Please Save in batches', { variant: 'warning' }); return; } try { const response = await EMSESP.writeMaskedEntities({ id: selectedDevice, entity_ids: masked_entities }); if (response.status === 200) { enqueueSnackbar('Customization saved', { variant: 'success' }); } else { enqueueSnackbar('Customization save failed', { variant: 'error' }); } } catch (error: any) { enqueueSnackbar(extractErrorMessage(error, 'Problem sending entity list'), { variant: 'error' }); } setInitialMask(deviceEntities); } }; const renderDeviceList = () => { if (!devices) { return ; } return ( <> Select a device and customize each of its entities using the options. Select a device... {devices.devices.sort(compareDevices).map((device: DeviceShort, index) => ( {device.s} ))} ); }; const renderDeviceData = () => { if (devices?.devices.length === 0 || deviceEntities[0].id === '') { return; } const shown_data = deviceEntities.filter( (de) => (de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase()) ); return ( <> #: {shown_data.length}/{deviceEntities.length} : { setSearch(event.target.value); }} /> : { setSelectedFilters(getMaskNumber(mask)); }} > : {(tableList: any) => ( <>
OPTIONS VALUE
{tableList.map((de: DeviceEntity) => ( { de.m = getMaskNumber(mask); if (de.m & DeviceEntityMask.DV_WEB_EXCLUDE) { de.m = de.m & ~DeviceEntityMask.DV_FAVORITE; } setMasks(['']); }} > {de.id} ({de.s}) {formatValue(de.v)} ))} )}
); }; const renderResetDialog = () => ( setConfirmReset(false)}> Reset Are you sure you want remove all customizations including the custom settings of the Temperature and Analog sensors? ); const content = () => { return ( <> Device Entities {renderDeviceList()} {renderDeviceData()} {renderResetDialog()} ); }; return ( {content()} ); }; export default SettingsCustomization;