- Add @tanstack/react-query 5.101.0; wrap app with QueryClientProvider - Add AvailabilityStatus type and AvailabilityResponse to api/types.ts - Implement useAvailabilityStatus (staleTime 30s, stale-on-error preserved) - Add AvailabilityStatusBadge with green/amber/red states and stale indicator - Replace DashboardPage placeholder card with live availability widget - Add MSW availability handler; update test/utils with QueryClientProvider - 55/55 tests pass (FR-05) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
6.4 KiB
Frontend Components — Unit 4: Dashboard
Component Hierarchy
graph TD
Main["main.tsx\n(App Entry)"]
QCP["QueryClientProvider\n(TanStack Query root)"]
AuthProv["AuthProvider"]
RouterProv["RouterProvider"]
DP["DashboardPage\n(/dashboard route)"]
Hook["useAvailabilityStatus\n(useQuery hook)"]
Welcome["Welcome Header\n(existing — unchanged)"]
Widget["Availability Widget\nsection"]
Badge["AvailabilityStatusBadge"]
Skeleton["Skeleton loading\n(isLoading)"]
ErrorBanner["Error Banner + Retry\n(isError)"]
StaleIndicator["Stale Indicator\n(isError + data)"]
Main --> QCP
QCP --> AuthProv
AuthProv --> RouterProv
RouterProv --> DP
DP --> Hook
DP --> Welcome
DP --> Widget
Widget --> Skeleton
Widget --> Badge
Widget --> ErrorBanner
Badge --> StaleIndicator
classDef infra fill:#4CAF50,stroke:#2E7D32,color:#000
classDef page fill:#2196F3,stroke:#0D47A1,color:#000
classDef hook fill:#9C27B0,stroke:#4A148C,color:#000
classDef ui fill:#FF9800,stroke:#E65100,color:#000
classDef error fill:#F44336,stroke:#B71C1C,color:#000
class Main,QCP,AuthProv,RouterProv infra
class DP page
class Hook hook
class Welcome,Widget,Badge,Skeleton,StaleIndicator ui
class ErrorBanner error
Component hierarchy: main.tsx wraps app with QueryClientProvider; DashboardPage consumes availability hook and renders welcome header + availability widget with conditional states.
New / Modified Files
1. frontend/src/main.tsx (modify)
Add QueryClientProvider wrapping above AuthProvider. No other changes.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: 1, refetchOnWindowFocus: true },
},
});
// Wrap existing tree:
<QueryClientProvider client={queryClient}>
<AuthProvider>
<RouterProvider router={router} context={{ auth }} />
</AuthProvider>
</QueryClientProvider>
2. frontend/src/api/types.ts (modify)
Add two new types at the end of the file:
export type AvailabilityStatus = 'Available' | 'Maintenance' | 'Unavailable';
export interface AvailabilityResponse {
status: AvailabilityStatus;
checkedAt: string; // ISO 8601
message: string;
}
3. frontend/src/api/useAvailability.ts (new)
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api-client';
import type { AvailabilityResponse } from './types';
export function useAvailabilityStatus() {
return useQuery<AvailabilityResponse>({
queryKey: ['availability', 'status'],
queryFn: () => api.get<AvailabilityResponse>('/api/v1/Availability/status'),
staleTime: 30_000,
});
}
Return: standard TanStack Query result (data, error, isLoading, isError, isFetching, refetch).
4. frontend/src/components/shared/AvailabilityStatusBadge.tsx (new)
Props:
| Prop | Type | Required | Description |
|---|---|---|---|
status |
AvailabilityStatus |
Yes | Current status value |
message |
string |
No | Subtitle text below badge (only shown when non-empty) |
stale |
boolean |
No | When true, renders a stale-data visual indicator (amber border, clock icon) |
Status → color mapping:
| Status | Badge color class | Icon |
|---|---|---|
Available |
bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 |
CheckCircle |
Maintenance |
bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 |
AlertTriangle |
Unavailable |
bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 |
XCircle |
stale prop behavior: When stale={true}, the badge wrapper gets an amber dashed border and a Clock icon + t('availability.staleLabel') label is rendered below the badge.
Test IDs:
| Element | data-testid |
|---|---|
| Badge wrapper | availability-badge |
| Status label | availability-status-label |
| Message subtitle | availability-message |
| Stale indicator | availability-stale-indicator |
5. frontend/src/pages/DashboardPage.tsx (modify)
Replace the generic placeholder Card with the availability widget section.
Conditional rendering logic:
if (isLoading) → show Skeleton in widget area
if (isError && !data) → show error card with Retry (no status to show)
if (data) → show AvailabilityStatusBadge
if (isError) → also show stale indicator on badge + error banner with Retry
Layout sketch (inside existing space-y-6 wrapper):
[Welcome header — unchanged]
[Card: title = t('availability.title')]
[if isLoading] → skeleton placeholder
[if !isLoading && data] → AvailabilityStatusBadge status={data.status} message={data.message} stale={isError}
[if isError] → error banner (below badge if stale, full-card if no data) + Retry button
6. frontend/src/mocks/availability/handlers.ts (new)
MSW handler for tests:
import { http, HttpResponse } from 'msw';
export const availabilityHandlers = [
http.get('*/Availability/status', () =>
HttpResponse.json({
status: 'Available',
checkedAt: new Date().toISOString(),
message: '',
})
),
];
Register in frontend/src/mocks/browser.ts and frontend/src/mocks/server.ts.
i18n Additions
en/translation.json:
{
"availability": {
"title": "System Status",
"available": "Available",
"maintenance": "Maintenance",
"unavailable": "Unavailable",
"errorTitle": "Could not load status",
"staleLabel": "Last known status",
"retry": "Retry"
}
}
nl/translation.json:
{
"availability": {
"title": "Systeemstatus",
"available": "Beschikbaar",
"maintenance": "Onderhoud",
"unavailable": "Niet beschikbaar",
"errorTitle": "Status kon niet worden geladen",
"staleLabel": "Laatste bekende status",
"retry": "Opnieuw proberen"
}
}
Test IDs Summary
| Element | data-testid |
|---|---|
| Availability badge wrapper | availability-badge |
| Status label text | availability-status-label |
| Message subtitle | availability-message |
| Stale indicator | availability-stale-indicator |
| Error banner | availability-error |
| Retry button | availability-retry |
| Widget skeleton | availability-skeleton |