feat(unit-4): Dashboard — availability widget with TanStack Query

- 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>
This commit is contained in:
2026-06-22 13:39:29 +02:00
co-authored by Claude Haiku 4.5
parent f476e06691
commit 3c6a06028e
23 changed files with 1219 additions and 22 deletions
@@ -0,0 +1,114 @@
# Business Logic Model — Unit 4: Dashboard
## Process Overview
Unit 4 introduces TanStack Query as a shared data-fetching layer and implements the Dashboard page with a live availability widget. The logic splits into three concerns: query infrastructure setup, the availability hook, and the dashboard UI composition.
---
## TanStack Query Setup
A `QueryClient` is created once at app startup and provided via `QueryClientProvider` wrapping the `RouterProvider` in `main.tsx`. This is a one-time global change that enables all future units to use `useQuery` / `useMutation`.
```ts
// Recommended default options for the QueryClient
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: true,
},
},
});
```
---
## useAvailabilityStatus Hook
**Location**: `frontend/src/api/useAvailability.ts`
**Query key**: `['availability', 'status']`
**Fetch function**: Calls `GET /api/v1/Availability/status` via the shared `api` client.
**staleTime**: `30_000` ms — TanStack Query refetches automatically in the background once data is 30 seconds old.
**Return shape**:
```ts
{
data: AvailabilityResponse | undefined; // undefined until first successful fetch
error: Error | null;
isLoading: boolean; // true only during initial fetch with no data
isFetching: boolean; // true during any in-flight refetch
isError: boolean;
refetch: () => void; // exposed for Retry button
}
```
**Stale-with-error state**: When a background refetch fails, TanStack Query preserves the previous `data` value while setting `isError = true`. This enables the dashboard to display the last known status alongside the error state (BR-U4-11).
---
## DashboardPage Composition
The DashboardPage composes existing layout primitives with the new availability widget. No routing changes are needed — `DashboardPage` is already registered as the `/dashboard` route component in `router.tsx`.
**Sections**:
1. **Header** — welcome message with user name (already implemented; retained as-is)
2. **Availability Widget** — replaces the generic placeholder card
---
## Availability Widget Logic Flow
```mermaid
sequenceDiagram
box rgba(99,179,237,0.4) Component Layer
participant DP as DashboardPage
participant Badge as AvailabilityStatusBadge
end
box rgba(154,230,180,0.4) Query Layer
participant Q as useAvailabilityStatus
end
box rgba(233,213,255,0.4) External
participant API as GET /availability/status
end
DP->>Q: mount — useAvailabilityStatus()
Q->>API: fetch on mount
API-->>Q: 200 AvailabilityResponse
Q-->>DP: data + isLoading=false
DP->>Badge: render with status and message
Note over Q: staleTime 30s elapses
Q->>API: background refetch
alt Refetch succeeds
API-->>Q: 200 AvailabilityResponse
Q-->>DP: updated data
DP->>Badge: re-render with fresh data
else Refetch fails
API-->>Q: network error or 5xx
Q-->>DP: isError=true data=stale
DP->>Badge: render stale badge with stale indicator
DP->>DP: show error banner with Retry button
end
```
Sequence showing initial fetch, badge render, background refetch on staleTime expiry, and error handling with stale data preservation.
---
## i18n Keys (additions)
New translation keys needed for the availability widget:
| Key | English | Dutch |
|---|---|---|
| `availability.title` | System Status | Systeemstatus |
| `availability.available` | Available | Beschikbaar |
| `availability.maintenance` | Maintenance | Onderhoud |
| `availability.unavailable` | Unavailable | Niet beschikbaar |
| `availability.errorTitle` | Could not load status | Status kon niet worden geladen |
| `availability.staleLabel` | Last known status | Laatste bekende status |
| `availability.retry` | Retry | Opnieuw proberen |