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:
@@ -0,0 +1,169 @@
|
||||
# Code Generation Plan — Unit 4: Dashboard
|
||||
|
||||
**Status**: ✅ Complete
|
||||
|
||||
## Unit Context
|
||||
|
||||
**Unit**: Unit 4 — Dashboard
|
||||
**Type**: Frontend (React/TypeScript)
|
||||
**Depends on**: Unit 3 (AppLayout, AuthContext) — complete
|
||||
**Stories Covered**: FR-05 (availability widget on dashboard)
|
||||
|
||||
**Key Design Decisions** (from functional design):
|
||||
- `@tanstack/react-query` added as new dependency; `QueryClientProvider` wraps the app in `main.tsx`
|
||||
- `useAvailabilityStatus` uses `useQuery` with `staleTime: 30_000` ms
|
||||
- Error + stale data: show last known status with stale indicator + error banner with Retry
|
||||
- `AvailabilityStatus` type and `AvailabilityResponse` interface added to `api/types.ts`
|
||||
- MSW mock handler created for tests
|
||||
|
||||
**Files to Modify**:
|
||||
- `frontend/package.json` — add `@tanstack/react-query`
|
||||
- `frontend/src/main.tsx` — wrap with `QueryClientProvider`
|
||||
- `frontend/src/api/types.ts` — add `AvailabilityStatus`, `AvailabilityResponse`
|
||||
- `frontend/src/mocks/index.ts` — register availability handlers
|
||||
- `frontend/src/pages/DashboardPage.tsx` — replace placeholder with availability widget
|
||||
- `frontend/src/i18n/locales/en/translation.json` — add `availability.*` keys
|
||||
- `frontend/src/i18n/locales/nl/translation.json` — add `availability.*` keys
|
||||
|
||||
**Files to Create**:
|
||||
- `frontend/src/api/useAvailability.ts`
|
||||
- `frontend/src/components/shared/AvailabilityStatusBadge.tsx`
|
||||
- `frontend/src/mocks/availability/handlers.ts`
|
||||
- `frontend/src/api/useAvailability.test.ts`
|
||||
- `frontend/src/components/shared/AvailabilityStatusBadge.test.tsx`
|
||||
- `aidlc-docs/features/cms-frontend/construction/unit-4/code/code-generation-summary.md`
|
||||
|
||||
---
|
||||
|
||||
## Code Generation Steps
|
||||
|
||||
### Step 1: Install `@tanstack/react-query`
|
||||
- [x] Run `pnpm add @tanstack/react-query` in `frontend/`
|
||||
- [x] Verify `package.json` updated with `@tanstack/react-query`
|
||||
|
||||
### Step 2: Add `AvailabilityStatus` and `AvailabilityResponse` to `api/types.ts`
|
||||
- [x] Append to `frontend/src/api/types.ts`:
|
||||
```ts
|
||||
export type AvailabilityStatus = 'Available' | 'Maintenance' | 'Unavailable';
|
||||
|
||||
export interface AvailabilityResponse {
|
||||
status: AvailabilityStatus;
|
||||
checkedAt: string; // ISO 8601
|
||||
message: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Wrap app with `QueryClientProvider` in `main.tsx`
|
||||
- [x] Import `QueryClient`, `QueryClientProvider` from `@tanstack/react-query`
|
||||
- [x] Create `queryClient` instance with `defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: true } }`
|
||||
- [x] Wrap `<AuthProvider>` with `<QueryClientProvider client={queryClient}>`
|
||||
|
||||
### Step 4: Create `useAvailability.ts`
|
||||
- [x] Create `frontend/src/api/useAvailability.ts`
|
||||
- [x] Implement `useAvailabilityStatus()` using `useQuery`:
|
||||
- `queryKey: ['availability', 'status']`
|
||||
- `queryFn`: `api.get<AvailabilityResponse>('/api/v1/Availability/status')`
|
||||
- `staleTime: 30_000`
|
||||
- [x] Return full TanStack Query result (`data`, `error`, `isLoading`, `isError`, `isFetching`, `refetch`)
|
||||
|
||||
### Step 5: Create `AvailabilityStatusBadge.tsx`
|
||||
- [x] Create `frontend/src/components/shared/AvailabilityStatusBadge.tsx`
|
||||
- [x] Props: `status: AvailabilityStatus`, `message?: string`, `stale?: boolean`
|
||||
- [x] Status → color/icon mapping:
|
||||
- `Available` → `bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200` + `CheckCircle` icon
|
||||
- `Maintenance` → `bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200` + `AlertTriangle` icon
|
||||
- `Unavailable` → `bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200` + `XCircle` icon
|
||||
- [x] Show `message` as subtitle paragraph when non-empty (`data-testid="availability-message"`)
|
||||
- [x] When `stale={true}`: amber dashed border + `Clock` icon + `t('availability.staleLabel')` label (`data-testid="availability-stale-indicator"`)
|
||||
- [x] Test IDs: `availability-badge` (wrapper), `availability-status-label`
|
||||
|
||||
### Step 6: Update `DashboardPage.tsx`
|
||||
- [x] Import `useAvailabilityStatus`, `AvailabilityStatusBadge` and `Card*` components
|
||||
- [x] Replace generic placeholder `Card` with the availability widget:
|
||||
- Card title: `t('availability.title')`
|
||||
- `isLoading` → skeleton placeholder (`data-testid="availability-skeleton"`)
|
||||
- `data` present → `<AvailabilityStatusBadge status={data.status} message={data.message} stale={isError} />`
|
||||
- `isError && !data` → error-only state: `t('availability.errorTitle')` + Retry button
|
||||
- `isError` (any) → error banner below/above badge with Retry button (`data-testid="availability-error"`, `data-testid="availability-retry"`)
|
||||
- [x] Keep welcome header section unchanged
|
||||
|
||||
### Step 7: Add i18n keys
|
||||
- [x] Add to `frontend/src/i18n/locales/en/translation.json`:
|
||||
```json
|
||||
"availability": {
|
||||
"title": "System Status",
|
||||
"available": "Available",
|
||||
"maintenance": "Maintenance",
|
||||
"unavailable": "Unavailable",
|
||||
"errorTitle": "Could not load status",
|
||||
"staleLabel": "Last known status",
|
||||
"retry": "Retry"
|
||||
}
|
||||
```
|
||||
- [x] Add to `frontend/src/i18n/locales/nl/translation.json`:
|
||||
```json
|
||||
"availability": {
|
||||
"title": "Systeemstatus",
|
||||
"available": "Beschikbaar",
|
||||
"maintenance": "Onderhoud",
|
||||
"unavailable": "Niet beschikbaar",
|
||||
"errorTitle": "Status kon niet worden geladen",
|
||||
"staleLabel": "Laatste bekende status",
|
||||
"retry": "Opnieuw proberen"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 8: Create MSW availability handler
|
||||
- [x] Create `frontend/src/mocks/availability/handlers.ts`:
|
||||
```ts
|
||||
import { http, HttpResponse } from 'msw';
|
||||
export const availabilityHandlers = [
|
||||
http.get('*/Availability/status', () =>
|
||||
HttpResponse.json({
|
||||
status: 'Available',
|
||||
checkedAt: new Date().toISOString(),
|
||||
message: '',
|
||||
})
|
||||
),
|
||||
];
|
||||
```
|
||||
- [x] Modify `frontend/src/mocks/index.ts`:
|
||||
- Import `availabilityHandlers`
|
||||
- Spread into `handlers` array
|
||||
- Re-export `availabilityHandlers`
|
||||
|
||||
### Step 9: Tests — `useAvailabilityStatus`
|
||||
- [x] Create `frontend/src/api/useAvailability.test.ts`
|
||||
- [x] Test cases (using MSW + `renderHook` with `QueryClientProvider` wrapper):
|
||||
- Returns `isLoading=true` initially
|
||||
- Returns `data` on successful fetch (using default MSW handler)
|
||||
- Returns `isError=true` + preserves stale `data` on refetch failure (override handler to return 500)
|
||||
- `refetch()` triggers a new fetch
|
||||
|
||||
### Step 10: Tests — `AvailabilityStatusBadge`
|
||||
- [x] Create `frontend/src/components/shared/AvailabilityStatusBadge.test.tsx`
|
||||
- [x] Test cases:
|
||||
- `Available` → green badge, `t('availability.available')` label
|
||||
- `Maintenance` → amber badge, `t('availability.maintenance')` label
|
||||
- `Unavailable` → red badge, `t('availability.unavailable')` label
|
||||
- `message` prop: renders subtitle when non-empty, hidden when empty
|
||||
- `stale={true}`: renders stale indicator
|
||||
- `stale={false}` (default): no stale indicator
|
||||
|
||||
### Step 11: Final verification
|
||||
- [x] `pnpm build` — no TypeScript or Vite errors
|
||||
- [x] `pnpm lint` — no ESLint errors
|
||||
- [x] `pnpm test` — all tests pass (existing + new)
|
||||
- [x] Dev server: verify availability widget renders on dashboard, skeleton visible on load, Retry button functional
|
||||
|
||||
### Step 12: Write code generation summary
|
||||
- [x] Create `aidlc-docs/features/cms-frontend/construction/unit-4/code/code-generation-summary.md`
|
||||
- [x] List all created/modified files with paths
|
||||
- [x] Record test results
|
||||
|
||||
### Step 13: Commit
|
||||
- [x] Commit with message referencing Unit 4 / FR-05
|
||||
|
||||
---
|
||||
|
||||
## Total Steps: 13
|
||||
@@ -0,0 +1,105 @@
|
||||
# Functional Design Plan — Unit 4: Dashboard
|
||||
|
||||
**Status**: 🚧 In Progress
|
||||
|
||||
## Unit Context
|
||||
|
||||
**Unit**: Unit 4 — Dashboard
|
||||
**Type**: Frontend (React/TypeScript)
|
||||
**Depends on**: Unit 3 (AppLayout, AuthContext)
|
||||
**Stories Covered**: US-05 (FR-05 — Dashboard with availability widget)
|
||||
|
||||
**Key Deliverables**:
|
||||
- `frontend/src/api/useAvailability.ts` — `useAvailabilityStatus` hook with auto-refresh
|
||||
- `frontend/src/components/shared/AvailabilityStatusBadge.tsx` — colored status badge
|
||||
- Update `frontend/src/pages/DashboardPage.tsx` — replace placeholder with availability widget
|
||||
- `frontend/src/mocks/availability/handlers.ts` — MSW mock for tests
|
||||
- i18n keys for availability widget (en + nl)
|
||||
- Unit tests
|
||||
|
||||
**API Endpoint**:
|
||||
- `GET /api/v1/Availability/status` → `{ status: "Available" | "Maintenance" | "Unavailable", checkedAt: string, message: string }`
|
||||
|
||||
**Note**: `routes/index.tsx` from unit-of-work.md maps to `pages/DashboardPage.tsx` in the actual project structure (code-based router, not file-based).
|
||||
|
||||
---
|
||||
|
||||
## Functional Design Steps
|
||||
|
||||
- [x] Step 1: Analyze unit context and existing code patterns
|
||||
- [x] Step 2: Generate questions, collect answers
|
||||
- [x] Step 3: Create `domain-entities.md`
|
||||
- [x] Step 4: Create `business-rules.md`
|
||||
- [x] Step 5: Create `business-logic-model.md`
|
||||
- [x] Step 6: Create `frontend-components.md`
|
||||
- [ ] Step 7: Present completion message and await approval
|
||||
|
||||
---
|
||||
|
||||
## Questions
|
||||
|
||||
Please fill in the letter after each `[Answer]:` tag.
|
||||
|
||||
---
|
||||
|
||||
### Question 1: Availability message display
|
||||
|
||||
The API returns a `message` field alongside the status. When/how should this message be shown?
|
||||
|
||||
A) Display the message as a subtitle below the status badge — always visible when non-empty (recommended — informative without cluttering the UI)
|
||||
B) Show the message as a tooltip on hover/focus over the badge — only visible on interaction
|
||||
C) Do not display the `message` field — show status text only
|
||||
D) Other
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 2: Error state when availability fetch fails
|
||||
|
||||
If `GET /availability/status` returns an error or the network is unreachable, what should the dashboard show?
|
||||
|
||||
A) An error card with a generic "Could not load availability status" message + Retry button (recommended — communicates the issue without crashing; consistent with global error pattern)
|
||||
B) Hide the availability widget silently — show nothing in its place
|
||||
C) Show the last successfully fetched status with a "stale" indicator (clock icon or "last updated X ago")
|
||||
D) Other
|
||||
|
||||
[Answer]: D, A combination of A and C. Show the error message with a retry button, but also tell the user the last fetched status for clarity
|
||||
|
||||
---
|
||||
|
||||
### Question 3: Auto-refresh interval
|
||||
|
||||
The unit-of-work specifies "staleTime: 30s". Since there is no TanStack Query, this will be implemented as `setInterval`. Should the hook auto-refresh while the component is mounted?
|
||||
|
||||
A) Yes — poll every 30 seconds while mounted (recommended — keeps dashboard current without manual refresh)
|
||||
B) Yes — but use a longer interval: 60 seconds
|
||||
C) No — fetch once on mount only; no auto-refresh (user can refresh the page)
|
||||
D) Other
|
||||
|
||||
[Answer]: D, try again to implement tanstack Query
|
||||
|
||||
---
|
||||
|
||||
### Question 4: AvailabilityStatus type location
|
||||
|
||||
Where should the `AvailabilityStatus` type and `AvailabilityResponse` interface be defined?
|
||||
|
||||
A) Add to `frontend/src/api/types.ts` — the shared types file (recommended — consistent with existing `User`, `AuthResponse`, `SetupStatus` types)
|
||||
B) Define inline in `useAvailability.ts` only — no shared types needed for this unit
|
||||
C) Other
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 5: Unit test scope
|
||||
|
||||
Which parts of Unit 4 should have unit tests?
|
||||
|
||||
A) `useAvailabilityStatus` hook (fetch, polling, error handling) + `AvailabilityStatusBadge` (renders correct colors/labels for each status) (recommended — covers all logic and UI variants)
|
||||
B) Only `AvailabilityStatusBadge` — skip hook tests (hook logic is simple enough to trust without tests)
|
||||
C) Full integration test: DashboardPage renders with availability widget (using MSW mock)
|
||||
D) Other
|
||||
|
||||
[Answer]: A
|
||||
Reference in New Issue
Block a user