import { useContext, useEffect, useState } from 'react'; import { IconContext } from 'react-icons/lib'; import { toast } from 'react-toastify'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import EditIcon from '@mui/icons-material/Edit'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; import { Box, IconButton, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from '@mui/material'; import Grid from '@mui/material/Grid2'; import { Body, Cell, Row, Table } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { CellTree, useTree } from '@table-library/react-table-library/tree'; import { useRequest } from 'alova/client'; import { FormLoader, SectionContent, useLayoutTitle } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { useInterval, usePersistState } from 'utils'; import { readDashboard, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; import DevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; import { type DashboardItem, DeviceEntityMask, DeviceType, type DeviceValue } from './types'; import { deviceValueItemValidation } from './validators'; const Dashboard = () => { const { LL } = useI18nContext(); const { me } = useContext(AuthenticatedContext); useLayoutTitle(LL.DASHBOARD()); const [showAll, setShowAll] = usePersistState(true, 'showAll'); const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); const [parentNodes, setParentNodes] = useState(0); const [selectedDashboardItem, setSelectedDashboardItem] = useState(); const { data, send: fetchDashboard, error, loading } = useRequest(readDashboard, { initialData: [] }).onSuccess((event) => { if (event.data.length !== parentNodes) { setParentNodes(event.data.length); // count number of parents/devices } }); const { loading: submitting, send: sendDeviceValue } = useRequest( (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data), { immediate: false } ); const deviceValueDialogSave = async (devicevalue: DeviceValue) => { if (!selectedDashboardItem) { return; } const id = selectedDashboardItem.parentNode.id; // this is the parent ID await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v }) .then(() => { toast.success(LL.WRITE_CMD_SENT()); }) .catch((error: Error) => { toast.error(error.message); }) .finally(() => { setDeviceValueDialogOpen(false); setSelectedDashboardItem(undefined); }); }; const dashboard_theme = useTheme({ Table: ` --data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px; `, BaseRow: ` font-size: 14px; .td { height: 28px; } `, Row: ` cursor: pointer; background-color: #1e1e1e; &:nth-of-type(odd) .td { background-color: #303030; }, &:hover .td { background-color: #177ac9; }, `, BaseCell: ` &:nth-of-type(2) { text-align: right; } &:nth-of-type(3) { text-align: right; } ` }); const tree = useTree( { nodes: data }, { onChange: undefined // not used but needed }, { treeIcon: { margin: '4px', iconDefault: null, iconRight: ( ), iconDown: ( ) }, indentation: 45 } ); useInterval(() => { if (!deviceValueDialogOpen) { void fetchDashboard(); } }); useEffect(() => { showAll ? tree.fns.onAddAll(data.map((item: DashboardItem) => item.id)) // expand tree : tree.fns.onRemoveAll(); // collapse tree }, [parentNodes]); const showType = (n?: string, t?: number) => { // if we have a name show it if (n) { return n; } if (t) { // otherwise pick translation based on type switch (t) { case DeviceType.CUSTOM: return LL.CUSTOM_ENTITIES(0); case DeviceType.ANALOGSENSOR: return LL.ANALOG_SENSORS(); case DeviceType.TEMPERATURESENSOR: return LL.TEMP_SENSORS(); case DeviceType.SCHEDULER: return LL.SCHEDULER(); default: break; } } return ''; }; const showName = (di: DashboardItem) => { if (di.id < 100) { // if its a device (parent node) and has entities if (di.nodes?.length) { return (   {showType(di.n, di.t)}  ({di.nodes?.length}) ); } } if (di.dv) { return {di.dv.id.slice(2)}; } }; const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; const editDashboardValue = (di: DashboardItem) => { if (me.admin && di.dv?.c) { setSelectedDashboardItem(di); setDeviceValueDialogOpen(true); } }; const handleShowAll = ( event: React.MouseEvent, toggle: boolean | null ) => { if (toggle !== null) { tree.fns.onToggleAll({}); setShowAll(toggle); } }; const renderContent = () => { if (!data) { return ; } return ( <> {LL.DASHBOARD_1()}. {!loading && data.length === 0 ? ( {LL.NO_DATA()} ) : ( {(tableList: DashboardItem[]) => ( {tableList.map((di: DashboardItem) => ( editDashboardValue(di)} > {di.id > 99 ? ( <> {showName(di)} {formatValue(LL, di.dv?.v, di.dv?.u)} {me.admin && di.dv?.c && !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( editDashboardValue(di)} > )} ) : ( <> {showName(di)} )} ))} )}
)}
); }; return ( {renderContent()} {selectedDashboardItem && selectedDashboardItem.dv && ( setDeviceValueDialogOpen(false)} onSave={deviceValueDialogSave} selectedItem={selectedDashboardItem.dv} writeable={true} validator={deviceValueItemValidation(selectedDashboardItem.dv)} progress={submitting} /> )} ); }; export default Dashboard;