mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
Dashboard - #1958
This commit is contained in:
@@ -3,6 +3,7 @@ import { IconContext } from 'react-icons/lib';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
|
import CircleIcon from '@mui/icons-material/Circle';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||||
@@ -12,8 +13,10 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
|
||||||
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
@@ -24,11 +27,17 @@ import { AuthenticatedContext } from 'contexts/authentication';
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
import { readDashboard, writeDeviceValue } from '../../api/app';
|
import { readDashboard, writeDeviceValue, writeSchedule } from '../../api/app';
|
||||||
import DeviceIcon from './DeviceIcon';
|
import DeviceIcon from './DeviceIcon';
|
||||||
import DashboardDevicesDialog from './DevicesDialog';
|
import DevicesDialog from './DevicesDialog';
|
||||||
import { formatValue } from './deviceValue';
|
import { formatValue } from './deviceValue';
|
||||||
import { type DashboardItem, DeviceEntityMask, type DeviceValue } from './types';
|
import {
|
||||||
|
type DashboardItem,
|
||||||
|
DeviceEntityMask,
|
||||||
|
DeviceType,
|
||||||
|
type DeviceValue,
|
||||||
|
type Schedule
|
||||||
|
} from './types';
|
||||||
import { deviceValueItemValidation } from './validators';
|
import { deviceValueItemValidation } from './validators';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
@@ -54,6 +63,13 @@ const Dashboard = () => {
|
|||||||
initialData: []
|
initialData: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { send: updateSchedule } = useRequest(
|
||||||
|
(data: Schedule) => writeSchedule(data),
|
||||||
|
{
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { loading: submitting, send: sendDeviceValue } = useRequest(
|
const { loading: submitting, send: sendDeviceValue } = useRequest(
|
||||||
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
|
(data: { id: number; c: string; v: unknown }) => writeDeviceValue(data),
|
||||||
{
|
{
|
||||||
@@ -104,14 +120,24 @@ const Dashboard = () => {
|
|||||||
const tree = useTree(
|
const tree = useTree(
|
||||||
{ nodes: data },
|
{ nodes: data },
|
||||||
{
|
{
|
||||||
onChange: null // not used but needed
|
onChange: undefined // not used but needed
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
treeIcon: {
|
treeIcon: {
|
||||||
margin: '4px',
|
margin: '4px',
|
||||||
iconDefault: null,
|
iconDefault: null,
|
||||||
iconRight: <ChevronRightIcon color="primary" />,
|
iconRight: (
|
||||||
iconDown: <ExpandMoreIcon color="primary" />
|
<ChevronRightIcon
|
||||||
|
sx={{ fontSize: 16, verticalAlign: 'middle' }}
|
||||||
|
color="info"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
iconDown: (
|
||||||
|
<ExpandMoreIcon
|
||||||
|
sx={{ fontSize: 16, verticalAlign: 'middle' }}
|
||||||
|
color="info"
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
indentation: 50
|
indentation: 50
|
||||||
}
|
}
|
||||||
@@ -131,27 +157,78 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
}, [loading]);
|
}, [loading]);
|
||||||
|
|
||||||
|
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_SENSOR(0);
|
||||||
|
case DeviceType.TEMPERATURESENSOR:
|
||||||
|
return LL.TEMP_SENSOR();
|
||||||
|
case DeviceType.SCHEDULER:
|
||||||
|
return LL.SCHEDULER();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
const showName = (di: DashboardItem) => {
|
const showName = (di: DashboardItem) => {
|
||||||
if (di.id < 100) {
|
if (di.id < 100) {
|
||||||
// if its a device and has entities
|
// if its a device (parent node) and has entities
|
||||||
if (di.nodes?.length) {
|
if (di.nodes?.length) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span style="font-size: 14px">
|
<span style="font-size: 14px">
|
||||||
<DeviceIcon type_id={di.t ?? 0} />
|
<DeviceIcon type_id={di.t ?? 0} />
|
||||||
{di.n}
|
{showType(di.n, di.t)}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
<span style={{ color: 'lightblue' }}> ({di.nodes?.length})</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <span style="color:lightgrey">{di.dv ? di.dv.id.slice(2) : ''}</span>;
|
if (di.dv) {
|
||||||
|
return (
|
||||||
|
// ids for scheduler, and sensors are between 9600 and 9900
|
||||||
|
<span style="color:lightgrey">
|
||||||
|
{di.id >= 9600 && di.id < 9900 ? di.dv.id : di.dv.id.slice(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasMask = (id: string, mask: number) =>
|
const hasMask = (id: string, mask: number) =>
|
||||||
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
(parseInt(id.slice(0, 2), 16) & mask) === mask;
|
||||||
|
|
||||||
|
const toggleSchedule = async (di: DashboardItem) => {
|
||||||
|
// create a dummy record, the id=0 picks it up
|
||||||
|
await updateSchedule({
|
||||||
|
schedule: {
|
||||||
|
id: 0, // special number for only changing the active flag
|
||||||
|
active: !di.dv?.v,
|
||||||
|
flags: 0, // unused
|
||||||
|
time: '', // unused
|
||||||
|
cmd: '', // unused
|
||||||
|
value: '', // unused
|
||||||
|
name: di.dv?.id ?? ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await fetchDashboard();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const editDashboardValue = (di: DashboardItem) => {
|
const editDashboardValue = (di: DashboardItem) => {
|
||||||
setSelectedDashboardItem(di);
|
setSelectedDashboardItem(di);
|
||||||
setDeviceValueDialogOpen(true);
|
setDeviceValueDialogOpen(true);
|
||||||
@@ -174,32 +251,37 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography mb={2} variant="body1" color="warning">
|
<Grid container spacing={0} justifyContent="flex-start">
|
||||||
{LL.DASHBOARD_1()}
|
<Grid size={11}>
|
||||||
</Typography>
|
<Typography mb={2} variant="body1" color="warning">
|
||||||
|
{LL.DASHBOARD_1()}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<ToggleButtonGroup
|
<Grid size={1} alignItems="end">
|
||||||
color="primary"
|
<ToggleButtonGroup
|
||||||
size="small"
|
color="primary"
|
||||||
value={showAll}
|
size="small"
|
||||||
exclusive
|
value={showAll}
|
||||||
onChange={handleShowAll}
|
exclusive
|
||||||
>
|
onChange={handleShowAll}
|
||||||
<ToggleButton value={true}>
|
>
|
||||||
<UnfoldMoreIcon fontSize="small" />
|
<ToggleButton value={true}>
|
||||||
</ToggleButton>
|
<UnfoldMoreIcon sx={{ fontSize: 14 }} />
|
||||||
<ToggleButton value={false}>
|
</ToggleButton>
|
||||||
<UnfoldLessIcon fontSize="small" />
|
<ToggleButton value={false}>
|
||||||
</ToggleButton>
|
<UnfoldLessIcon sx={{ fontSize: 14 }} />
|
||||||
</ToggleButtonGroup>
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
mt={2}
|
|
||||||
padding={1}
|
padding={1}
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 2,
|
borderRadius: 1,
|
||||||
border: '1px solid grey'
|
border: '1px solid grey'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -225,30 +307,65 @@ const Dashboard = () => {
|
|||||||
<Body>
|
<Body>
|
||||||
{tableList.map((di: DashboardItem) => (
|
{tableList.map((di: DashboardItem) => (
|
||||||
<Row key={di.id} item={di}>
|
<Row key={di.id} item={di}>
|
||||||
{/* TODO add a comment about the number 99 */}
|
|
||||||
{di.id > 99 ? (
|
{di.id > 99 ? (
|
||||||
<Cell>{showName(di)}</Cell>
|
<>
|
||||||
) : (
|
<Cell>{showName(di)}</Cell>
|
||||||
<CellTree item={di}>{showName(di)}</CellTree>
|
<Cell>
|
||||||
)}
|
<Tooltip
|
||||||
<Cell pinRight>
|
placement="left"
|
||||||
<span style={{ color: 'lightgrey' }}>
|
title={
|
||||||
{di.dv && formatValue(LL, di.dv.v, di.dv.u)}
|
di.dv ? formatValue(LL, di.dv.v, di.dv.u) : ''
|
||||||
</span>
|
}
|
||||||
</Cell>
|
arrow
|
||||||
|
|
||||||
<Cell stiff>
|
|
||||||
{me.admin &&
|
|
||||||
di.dv?.c &&
|
|
||||||
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => editDashboardValue(di)}
|
|
||||||
>
|
>
|
||||||
<EditIcon color="primary" sx={{ fontSize: 16 }} />
|
<span style={{ color: 'lightgrey' }}>
|
||||||
</IconButton>
|
{di.dv ? formatValue(LL, di.dv.v, di.dv.u) : ''}
|
||||||
)}
|
</span>
|
||||||
</Cell>
|
</Tooltip>
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
<Cell stiff pinRight>
|
||||||
|
{me.admin && di.id < 9700 && di.id >= 9600 ? (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => toggleSchedule(di)}
|
||||||
|
>
|
||||||
|
{di.dv?.v ? (
|
||||||
|
<CircleIcon
|
||||||
|
color="success"
|
||||||
|
sx={{ fontSize: 16, verticalAlign: 'middle' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CircleIcon
|
||||||
|
color="error"
|
||||||
|
sx={{ fontSize: 16, verticalAlign: 'middle' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
me.admin &&
|
||||||
|
di.dv?.c &&
|
||||||
|
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => editDashboardValue(di)}
|
||||||
|
>
|
||||||
|
<EditIcon
|
||||||
|
color="primary"
|
||||||
|
sx={{ fontSize: 16 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Cell>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CellTree item={di}>{showName(di)}</CellTree>
|
||||||
|
<Cell />
|
||||||
|
<Cell />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
</Body>
|
</Body>
|
||||||
@@ -265,7 +382,7 @@ const Dashboard = () => {
|
|||||||
<SectionContent>
|
<SectionContent>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
{selectedDashboardItem && selectedDashboardItem.dv && (
|
{selectedDashboardItem && selectedDashboardItem.dv && (
|
||||||
<DashboardDevicesDialog
|
<DevicesDialog
|
||||||
open={deviceValueDialogOpen}
|
open={deviceValueDialogOpen}
|
||||||
onClose={() => setDeviceValueDialogOpen(false)}
|
onClose={() => setDeviceValueDialogOpen(false)}
|
||||||
onSave={deviceValueDialogSave}
|
onSave={deviceValueDialogSave}
|
||||||
|
|||||||
Reference in New Issue
Block a user