Genesis
This commit is contained in:
85
src/client/Dashboard.js
Normal file
85
src/client/Dashboard.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Grid, Typography, Button, ButtonGroup, Box, Alert } from '@mui/material';
|
||||
import ControllerCard from './ControllerCard';
|
||||
|
||||
export default function Dashboard() {
|
||||
const [groupedDevices, setGroupedDevices] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [range, setRange] = useState('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.
|
||||
|
||||
const res = await fetch('api/devices');
|
||||
if (!res.ok) throw new Error('Failed to fetch devices');
|
||||
|
||||
const devices = await res.json();
|
||||
|
||||
// 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;
|
||||
}, {});
|
||||
|
||||
setGroupedDevices(grouped);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(err.message);
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
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>Loading devices...</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'}
|
||||
>
|
||||
24 Hours
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setRange('week')}
|
||||
color={range === 'week' ? 'primary' : 'inherit'}
|
||||
>
|
||||
7 Days
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setRange('month')}
|
||||
color={range === 'month' ? 'primary' : 'inherit'}
|
||||
>
|
||||
30 Days
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
|
||||
{Object.entries(groupedDevices).map(([controllerName, ports]) => (
|
||||
<ControllerCard
|
||||
key={controllerName}
|
||||
controllerName={controllerName}
|
||||
ports={ports}
|
||||
range={range}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user