Files
slp-modular-cms/aidlc-docs/features/cms-frontend/construction/unit-4/functional-design/frontend-components.md
T
SluijsensandClaude Haiku 4.5 3c6a06028e 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>
2026-06-22 13:39:29 +02:00

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