This commit is contained in:
sebseb7
2025-12-25 00:08:05 +01:00
parent 1f3292bc17
commit 077e76735e
15 changed files with 9311 additions and 0 deletions

109
uiserver/src/components/Chart.js vendored Normal file
View File

@@ -0,0 +1,109 @@
import React, { useEffect, useState } from 'react';
import { Paper, Typography, Box, CircularProgress } from '@mui/material';
import { LineChart } from '@mui/x-charts/LineChart';
import { useTheme } from '@mui/material/styles';
export default function Chart({ selectedChannels = [], channelConfig = null }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const theme = useTheme();
// Determine effective channels list
const effectiveChannels = channelConfig
? channelConfig.map(c => c.id)
: selectedChannels;
const fetchData = () => {
// Only fetch if selection exists
if (effectiveChannels.length === 0) {
setData([]);
setLoading(false);
return;
}
const selectionStr = effectiveChannels.join(',');
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
fetch(`/api/readings?selection=${encodeURIComponent(selectionStr)}&since=${since}`)
.then(res => res.json())
.then(rows => {
const timeMap = new Map();
rows.forEach(row => {
const id = `${row.device}:${row.channel}`;
if (!effectiveChannels.includes(id)) return;
const time = new Date(row.timestamp).getTime();
if (!timeMap.has(time)) {
timeMap.set(time, { time: new Date(row.timestamp) });
}
const entry = timeMap.get(time);
let val = row.value;
if (row.data_type === 'json' && !val) {
val = null;
}
entry[id] = val;
});
const sortedData = Array.from(timeMap.values()).sort((a, b) => a.time - b.time);
setData(sortedData);
setLoading(false);
})
.catch(err => {
console.error("Failed to fetch data", err);
setLoading(false);
});
};
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 60000);
return () => clearInterval(interval);
}, [selectedChannels, channelConfig]);
if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
if (effectiveChannels.length === 0) return <Box sx={{ p: 4 }}><Typography>No channels selected.</Typography></Box>;
const series = effectiveChannels.map(id => {
// Find alias if config exists
let label = id;
if (channelConfig) {
const item = channelConfig.find(c => c.id === id);
if (item && item.alias) label = item.alias;
}
return {
dataKey: id,
label: label,
connectNulls: true,
showMark: false,
};
});
return (
<Box sx={{ width: '100%', height: '80vh', p: 2 }}>
<Typography variant="h6" gutterBottom>Last 24 Hours</Typography>
<Paper sx={{ p: 2, height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ flexGrow: 1 }}>
<LineChart
dataset={data}
series={series}
xAxis={[{
dataKey: 'time',
scaleType: 'time',
valueFormatter: (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}]}
slotProps={{
legend: {
direction: 'row',
position: { vertical: 'top', horizontal: 'middle' },
padding: 0,
},
}}
/>
</Box>
</Paper>
</Box>
);
}