89 lines
2.9 KiB
JavaScript
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>
|
|
);
|
|
}
|