wulf-pulse/app/mobile/dashboard/page.tsx
lorentz 9658640c04 fix(04-01): restore phase 2/3 work lost by worktree soft-reset
The soft reset to 77073ba inadvertently staged deletions of all phase 2
and 3 artifacts. This commit restores them from their source commits so
subsequent task commits build on the complete prior-phase foundation:
- components/mobile/{BottomNav,HeaderBar,KpiCardMobile,MoreDrawer,NeedsAttentionStrip,WorkerStatusRow}
- app/mobile/layout.tsx, dashboard/page.tsx, analyzer/page.tsx
- app/api/mobile/dashboard/route.ts
- All .planning/** files from phases 01-04
- CLAUDE.md, app/layout.tsx, app/styles/brand.css, public/manifest.json
2026-05-03 18:01:14 -04:00

118 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
/* /mobile/dashboard — phase 03 (DASH-01..04).
*
* Three sections, top-to-bottom:
* 1. 2×2 KPI grid (DASH-01)
* 2. Needs Attention (DASH-02)
* 3. Worker/backup row (DASH-03)
*
* No charts on phone widths (DASH-04). Header + bottom nav are provided
* by app/mobile/layout.tsx; this page only renders the H1 and body. */
import { useEffect, useState } from 'react';
import { RefreshCw } from 'lucide-react';
import { KpiCardMobile } from '@/components/mobile/KpiCardMobile';
import { NeedsAttentionStrip } from '@/components/mobile/NeedsAttentionStrip';
import { WorkerStatusRow } from '@/components/mobile/WorkerStatusRow';
import type { MobileDashboardResponse } from '@/app/api/mobile/dashboard/route';
export default function MobileDashboard() {
const [data, setData] = useState<MobileDashboardResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
async function load() {
setLoading(true);
setError(null);
try {
const r = await fetch('/api/mobile/dashboard');
if (!r.ok) {
const body = (await r.json().catch(() => ({}))) as { error?: string; message?: string };
throw new Error(body.message ?? body.error ?? `HTTP ${r.status}`);
}
setData((await r.json()) as MobileDashboardResponse);
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error');
} finally {
setLoading(false);
}
}
useEffect(() => { void load(); }, []);
return (
<div className="p-4 space-y-5">
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold">Dashboard</h1>
<button
type="button"
onClick={load}
disabled={loading}
aria-label="Refresh dashboard"
className="p-2 rounded-full hover:bg-accent disabled:opacity-40"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
</button>
</div>
{error && !loading && (
<div className="rounded-xl border border-destructive/50 bg-destructive/5 p-4">
<p className="text-sm font-medium text-destructive">Failed to load</p>
<p className="text-xs text-muted-foreground mt-1">{error}</p>
<button
type="button"
onClick={load}
className="mt-3 text-xs font-medium text-primary hover:underline"
>
Retry
</button>
</div>
)}
{loading && !data && (
<div className="flex items-center justify-center h-64">
<RefreshCw className="w-6 h-6 animate-spin text-muted-foreground" />
</div>
)}
{data && (
<>
{/* DASH-01: 2×2 KPI grid */}
<div className="grid grid-cols-2 gap-3">
{data.kpis.map(kpi => (
<KpiCardMobile
key={kpi.id}
label={kpi.label}
value={kpi.value}
caption={kpi.caption}
tone={kpi.tone ?? 'default'}
/>
))}
</div>
{/* DASH-02: Needs Attention horizontal strip */}
<NeedsAttentionStrip
items={data.needsAttention.map(a => ({
id: a.id,
label: a.label,
count: a.count,
href: a.href,
}))}
/>
{/* DASH-03: Worker/backup status row */}
<WorkerStatusRow
entries={data.workers.map(w => ({
id: w.id,
label: w.label,
value: w.value,
status: w.status,
href: w.href,
}))}
/>
</>
)}
</div>
);
}