feat(tracker-web): add dashboard stats retry state
Some checks failed
Publish @bytelyst/* packages / publish (push) Failing after 13s
CI — Common Platform / Build, Test & Typecheck (push) Successful in 44s

This commit is contained in:
Saravana Kumar 2026-05-30 20:23:14 +00:00
parent 6c49296d40
commit c1a88a39e2
2 changed files with 70 additions and 12 deletions

View File

@ -278,6 +278,38 @@ test.describe('Tracker — Authenticated dashboard', () => {
await expect(page.getByText('admin@example.com')).toBeVisible();
});
test('shows a retry path when dashboard stats fail', async ({ page }) => {
await page.route('**/api/auth/me', (route: Route) =>
route.fulfill({
json: { id: 'u1', email: 'admin@example.com', role: 'admin', displayName: 'Admin' },
})
);
let allowRecovery = false;
await page.route('**/api/tracker/**', (route: Route) => {
if (route.request().url().includes('/items/stats')) {
return !allowRecovery
? route.fulfill({ status: 502, json: { error: 'upstream unavailable' } })
: route.fulfill({
json: {
productId: 'tracker-e2e',
total: 7,
byType: { bug: 1, feature: 5, task: 1 },
byStatus: { open: 4, in_progress: 2, done: 1 },
byPriority: { critical: 0, high: 2, medium: 4, low: 1 },
},
});
}
return route.fulfill({ json: {} });
});
await page.addInitScript(() => localStorage.setItem('tracker_token', 'fake-e2e-token'));
await page.goto('/dashboard');
await expect(page.getByRole('heading', { name: /could not load dashboard/i })).toBeVisible();
allowRecovery = true;
await page.getByRole('button', { name: /retry/i }).click();
await expect(page.getByTestId('bl-number-flow').filter({ hasText: '7' }).first()).toBeVisible();
});
test('renders settings for admin configuration', async ({ page }) => {
await page.route('**/api/auth/me', (route: Route) =>
route.fulfill({

View File

@ -1,12 +1,13 @@
'use client';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import { PageHeader, LoadingSpinner } from '@bytelyst/dashboard-components';
import { KpiCard } from '@bytelyst/data-viz';
import { Reveal, NumberFlow } from '@bytelyst/motion';
import { Skeleton, toast } from '@/components/ui/Primitives';
import { Button, Skeleton, toast } from '@/components/ui/Primitives';
import { useAuth } from '@/lib/auth-context';
import { useProduct } from '@/lib/product-context';
import { getStats, type TrackerStats } from '@/lib/tracker-client';
import { overviewKpis } from '@/lib/overview-charts';
@ -18,16 +19,31 @@ const OverviewCharts = dynamic(() => import('@/components/overview-charts'), {
export default function DashboardOverview() {
const { token } = useAuth();
const { productId } = useProduct();
const [stats, setStats] = useState<TrackerStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadStats = useCallback(async () => {
if (!token) return;
setLoading(true);
setError(null);
try {
const data = await getStats();
setStats(data);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Unknown error';
setStats(null);
setError(message);
toast({ type: 'error', title: 'Failed to load stats', description: message });
} finally {
setLoading(false);
}
}, [token]);
useEffect(() => {
if (!token) return;
getStats()
.then(setStats)
.catch(err =>
toast({ type: 'error', title: 'Failed to load stats', description: err.message })
);
}, [token]);
void loadStats();
}, [loadStats, productId]);
const kpis = stats ? overviewKpis(stats) : null;
@ -36,7 +52,17 @@ export default function DashboardOverview() {
<PageHeader title="Dashboard" />
<p className="-mt-4 text-sm text-muted-foreground">Overview of all tracked items</p>
{stats && kpis ? (
{error ? (
<div className="rounded-2xl border border-destructive/30 bg-card p-6 shadow-sm">
<h2 className="text-lg font-semibold text-foreground">Could not load dashboard</h2>
<p className="mt-2 text-sm text-muted-foreground">
{error}. Check platform-service health or retry the request.
</p>
<Button className="mt-4" onClick={() => void loadStats()}>
Retry
</Button>
</div>
) : stats && kpis ? (
<div className="space-y-4">
{/* KPI row */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
@ -57,11 +83,11 @@ export default function DashboardOverview() {
<OverviewCharts stats={stats} />
</Reveal>
</div>
) : (
) : loading ? (
<div className="flex justify-center py-10">
<LoadingSpinner />
</div>
)}
) : null}
</div>
);
}