--- phase: 04-frontend plan: 01 type: execute wave: 1 depends_on: [] files_modified: - frontend/src/services/adminService.ts - frontend/src/components/AlertBanner.tsx - frontend/src/components/AdminMonitoringDashboard.tsx autonomous: true requirements: - ALRT-03 - ANLY-02 - HLTH-01 must_haves: truths: - "adminService exposes typed methods for getHealth(), getAnalytics(range), getAlerts(), and acknowledgeAlert(id)" - "AlertBanner component renders critical active alerts with acknowledge button" - "AdminMonitoringDashboard component shows health status grid and analytics summary with range selector" artifacts: - path: "frontend/src/services/adminService.ts" provides: "Monitoring API client methods with typed interfaces" contains: "getHealth" - path: "frontend/src/components/AlertBanner.tsx" provides: "Global alert banner with acknowledge callback" exports: ["AlertBanner"] - path: "frontend/src/components/AdminMonitoringDashboard.tsx" provides: "Health panel + analytics summary panel" exports: ["AdminMonitoringDashboard"] key_links: - from: "frontend/src/components/AlertBanner.tsx" to: "adminService.ts" via: "AlertEvent type import" pattern: "import.*AlertEvent.*adminService" - from: "frontend/src/components/AdminMonitoringDashboard.tsx" to: "adminService.ts" via: "getHealth and getAnalytics calls" pattern: "adminService\\.(getHealth|getAnalytics)" --- Create the three building blocks for Phase 4 frontend: extend adminService with typed monitoring API methods, build the AlertBanner component, and build the AdminMonitoringDashboard component. Purpose: These components and service methods are the foundation that Plan 02 wires into the Dashboard. Separating creation from wiring keeps each plan focused. Output: Three files ready to be imported and mounted in App.tsx. @/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md @/home/jonathan/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-frontend/04-RESEARCH.md @frontend/src/services/adminService.ts @frontend/src/components/Analytics.tsx @frontend/src/components/UploadMonitoringDashboard.tsx @frontend/src/utils/cn.ts Task 1: Extend adminService with monitoring API methods and types frontend/src/services/adminService.ts Add three exported interfaces and four new methods to the existing AdminService class in `frontend/src/services/adminService.ts`. **Interfaces to add** (above the AdminService class): ```typescript export interface AlertEvent { id: string; service_name: string; alert_type: 'service_down' | 'service_degraded' | 'recovery'; status: 'active' | 'acknowledged' | 'resolved'; message: string | null; details: Record | null; created_at: string; acknowledged_at: string | null; resolved_at: string | null; } export interface ServiceHealthEntry { service: string; status: 'healthy' | 'degraded' | 'down' | 'unknown'; checkedAt: string | null; latencyMs: number | null; errorMessage: string | null; } export interface AnalyticsSummary { range: string; totalUploads: number; succeeded: number; failed: number; successRate: number; avgProcessingMs: number | null; generatedAt: string; } ``` **IMPORTANT type casing note** (from RESEARCH Pitfall 4): - `ServiceHealthEntry` uses camelCase (backend admin.ts remaps to camelCase) - `AlertEvent` uses snake_case (backend returns raw model data) - `AnalyticsSummary` uses camelCase (from backend analyticsService.ts) **Methods to add** inside the AdminService class: ```typescript async getHealth(): Promise { const response = await apiClient.get('/admin/health'); return response.data.data; } async getAnalytics(range: string = '24h'): Promise { const response = await apiClient.get(`/admin/analytics?range=${range}`); return response.data.data; } async getAlerts(): Promise { const response = await apiClient.get('/admin/alerts'); return response.data.data; } async acknowledgeAlert(id: string): Promise { const response = await apiClient.post(`/admin/alerts/${id}/acknowledge`); return response.data.data; } ``` Keep all existing methods and interfaces. Do not modify the `apiClient` interceptor or the `ADMIN_EMAIL` check. cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit --strict src/services/adminService.ts 2>&1 | head -20 Verify the file exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces and the four new methods adminService.ts exports 3 new typed interfaces and 4 new methods (getHealth, getAnalytics, getAlerts, acknowledgeAlert) alongside all existing functionality Task 2: Create AlertBanner and AdminMonitoringDashboard components frontend/src/components/AlertBanner.tsx, frontend/src/components/AdminMonitoringDashboard.tsx **AlertBanner.tsx** — Create a new component at `frontend/src/components/AlertBanner.tsx`: Props interface: ```typescript interface AlertBannerProps { alerts: AlertEvent[]; onAcknowledge: (id: string) => Promise; } ``` Behavior: - Filter alerts to show only `status === 'active'` AND `alert_type` is `service_down` or `service_degraded` (per RESEARCH Pitfall — `recovery` is informational, not critical) - If no critical alerts after filtering, return `null` - Render a red banner (`bg-red-600 px-4 py-3`) with each alert showing: - `AlertTriangle` icon from lucide-react (h-5 w-5, flex-shrink-0) - Text: `{alert.service_name}: {alert.message ?? alert.alert_type}` (text-sm font-medium text-white) - "Acknowledge" button with `X` icon from lucide-react (text-sm underline hover:no-underline) - `onAcknowledge` called with `alert.id` on button click - Import `AlertEvent` from `../services/adminService` - Import `cn` from `../utils/cn` - Use `AlertTriangle` and `X` from `lucide-react` **AdminMonitoringDashboard.tsx** — Create at `frontend/src/components/AdminMonitoringDashboard.tsx`: This component contains two sections: Service Health Panel and Processing Analytics Panel. State: ```typescript const [health, setHealth] = useState([]); const [analytics, setAnalytics] = useState(null); const [range, setRange] = useState('24h'); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); ``` Data fetching: Use `useCallback` + `useEffect` pattern matching existing `Analytics.tsx`: - `loadData()` calls `Promise.all([adminService.getHealth(), adminService.getAnalytics(range)])` - Sets loading/error state appropriately - Re-fetches when `range` changes **Service Health Panel:** - 2x2 grid on desktop (`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4`) - Each card: white bg, rounded-lg, shadow-soft, border border-gray-100, p-4 - Status dot: `w-3 h-3 rounded-full` with color mapping: - `healthy` → `bg-green-500` - `degraded` → `bg-yellow-500` - `down` → `bg-red-500` - `unknown` → `bg-gray-400` - Service display name mapping: `document_ai` → "Document AI", `llm_api` → "LLM API", `supabase` → "Supabase", `firebase_auth` → "Firebase Auth" - Show `checkedAt` as `new Date(checkedAt).toLocaleString()` if available, otherwise "Never checked" - Show `latencyMs` with "ms" suffix if available - Use `Activity`, `Clock` icons from lucide-react **Processing Analytics Panel:** - Range selector: `