This commit is contained in:
sebseb7
2025-12-23 05:58:03 +01:00
parent 84c47e3357
commit 5f4c4a55d8
22 changed files with 2935 additions and 2468 deletions

View File

@@ -1,92 +1,67 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { Component } from 'react';
import { Grid, Typography, Button, ButtonGroup, Box, Alert } from '@mui/material';
import ControllerCard from './ControllerCard';
import OutputChart from './OutputChart';
import { useI18n } from './I18nContext';
import { withI18n } from './I18nContext';
import { withDevices } from './DevicesContext';
export default function Dashboard() {
const { t } = useI18n();
const [groupedDevices, setGroupedDevices] = useState({});
const [allDevices, setAllDevices] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [range, setRange] = useState('day'); // 'day', 'week', 'month'
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
range: 'day' // 'day', 'week', 'month'
};
}
const fetchDevices = useCallback(async () => {
try {
// Robust API Base detection
const baseUrl = window.location.pathname.endsWith('/') ? 'api/' : 'api/';
// Actually, since we are serving from root or subpath, relative 'api/' is tricky if URL depth changes.
// Better to use a relative path that works from the page root.
// If page is /ac-dashboard/, fetch is /ac-dashboard/api/devices.
setRange = (range) => {
this.setState({ range });
};
const res = await fetch('api/devices');
if (!res.ok) throw new Error('Failed to fetch devices');
render() {
const { i18n: { t }, devicesCtx: { devices, groupedDevices, loading, error } } = this.props;
const { range } = this.state;
const devices = await res.json();
if (loading) return <Typography>{t('dashboard.loading')}</Typography>;
if (error) return <Alert severity="error">{error}</Alert>;
// Group by dev_name
const grouped = devices.reduce((acc, dev) => {
if (!acc[dev.dev_name]) acc[dev.dev_name] = [];
acc[dev.dev_name].push(dev);
return acc;
}, {});
return (
<Box>
<Box display="flex" justifyContent="flex-end" mb={3}>
<ButtonGroup variant="contained" aria-label="outlined primary button group">
<Button
onClick={() => this.setRange('day')}
color={range === 'day' ? 'primary' : 'inherit'}
>
{t('dashboard.hours24')}
</Button>
<Button
onClick={() => this.setRange('week')}
color={range === 'week' ? 'primary' : 'inherit'}
>
{t('dashboard.days7')}
</Button>
<Button
onClick={() => this.setRange('month')}
color={range === 'month' ? 'primary' : 'inherit'}
>
{t('dashboard.days30')}
</Button>
</ButtonGroup>
</Box>
setGroupedDevices(grouped);
setAllDevices(devices);
setLoading(false);
} catch (err) {
console.error(err);
setError(err.message);
setLoading(false);
}
}, []);
{Object.entries(groupedDevices).map(([controllerName, ports]) => (
<ControllerCard
key={controllerName}
controllerName={controllerName}
ports={ports}
range={range}
/>
))}
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
// Auto-refresh logic (basic rerender trigger could be added here,
// but simpler to let ControllerCard handle data fetching internally based on props)
if (loading) return <Typography>{t('dashboard.loading')}</Typography>;
if (error) return <Alert severity="error">{error}</Alert>;
return (
<Box>
<Box display="flex" justifyContent="flex-end" mb={3}>
<ButtonGroup variant="contained" aria-label="outlined primary button group">
<Button
onClick={() => setRange('day')}
color={range === 'day' ? 'primary' : 'inherit'}
>
{t('dashboard.hours24')}
</Button>
<Button
onClick={() => setRange('week')}
color={range === 'week' ? 'primary' : 'inherit'}
>
{t('dashboard.days7')}
</Button>
<Button
onClick={() => setRange('month')}
color={range === 'month' ? 'primary' : 'inherit'}
>
{t('dashboard.days30')}
</Button>
</ButtonGroup>
<OutputChart range={range} devices={devices} />
</Box>
{Object.entries(groupedDevices).map(([controllerName, ports]) => (
<ControllerCard
key={controllerName}
controllerName={controllerName}
ports={ports}
range={range}
/>
))}
<OutputChart range={range} devices={allDevices} />
</Box>
);
);
}
}
export default withDevices(withI18n(Dashboard));