Rapport_React/src/views/dashboard/index.jsx

89 lines
2.9 KiB
JavaScript

import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router';
import { Button, Card, Container, Row, Spinner } from 'react-bootstrap';
import { signedGet, isSuccess } from '@/api/client';
import PageBreadcrumb from '@/components/PageBreadcrumb';
import MetricCard from '@/views/dashboard/MetricCard';
import EmptyState from '@/components/EmptyState';
import { resolveWidget } from '@/views/dashboard/widgets/registry';
function WidgetSlot({ widget }) {
const Component = resolveWidget(widget.widget_name);
if (!Component) {
return (
<Card className="mb-3">
<Card.Body>
<h4 className="header-title mb-1">{widget.title || widget.widget_name}</h4>
<p className="text-muted mb-0">
Widget <code>{widget.widget_name}</code> is not configured.
</p>
</Card.Body>
</Card>
);
}
return <Component widget={widget} />;
}
export default function Dashboard() {
const [searchParams, setSearchParams] = useSearchParams();
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [metrics, setMetrics] = useState([]);
const [widgets, setWidgets] = useState([]);
const load = async (refresh) => {
setLoading(true);
setError('');
try {
const params = refresh ? { refresh: 1 } : {};
const [reportRes, widgetRes] = await Promise.all([
signedGet('metric', 'metric_report/list', params),
signedGet('wall', 'widgets/list'),
]);
if (reportRes?.statusCode === 401 || widgetRes?.statusCode === 401) {
setError('Unauthorised');
return;
}
setMetrics(isSuccess(reportRes) ? reportRes.data?.metrics || [] : []);
setWidgets(isSuccess(widgetRes) ? widgetRes.data?.widgets || [] : []);
} catch (err) {
setError(err.message || 'Unable to load dashboard');
} finally {
setLoading(false);
}
};
useEffect(() => {
load(searchParams.get('refresh') === '1');
}, [searchParams]);
return (
<Container fluid>
<PageBreadcrumb title="Dashboard" subtitle="Welcome" />
<div className="d-flex justify-content-end align-items-center mb-3">
<Button variant="dark" onClick={() => setSearchParams({ refresh: '1' })}>
Refresh Data
</Button>
</div>
{error ? <div className="alert alert-danger">{error}</div> : null}
{loading ? (
<div className="text-center py-5">
<Spinner animation="border" />
</div>
) : (
<>
{metrics.length === 0 && widgets.length === 0 ? <EmptyState /> : null}
<Row>
{metrics.map((metric) => (
<MetricCard key={metric.id || metric.name} metric={metric} />
))}
</Row>
{widgets.map((widget) => (
<WidgetSlot key={widget.id || widget.widget_name} widget={widget} />
))}
</>
)}
</Container>
);
}