frontend: What-If page with fan chart driven by /simulate
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
New /what-if route. Sticky form on the left (jurisdiction, strategy, glide, NW seed, spending, savings, horizon, optional floor for vpw_floor, MC paths) submits to POST /simulate; results panel renders summary stats + the new FanChart. FanChart component layers seven series: - p10 invisible baseline (line, transparent) - p10→p25 stacked area (low opacity) - p25→p75 stacked area (IQR, mid opacity) - p75→p90 stacked area (low opacity) - p50 solid median line (drawn last, prominent) - p10 + p90 dashed lines on top of the bands Stacking deltas keeps the band fills clean — plotting raw quantiles each as their own area would overlap badly. Reusable by scenario detail in the next chunk (same ProjectionPoint[] shape). 5 tests pass (was 2). 470 KB gzipped (ECharts). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5d2b9e931a
commit
bb74bc0add
4 changed files with 550 additions and 4 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Route, Routes, Link } from 'react-router-dom';
|
||||
import { NavLink, Route, Routes, Link } from 'react-router-dom';
|
||||
|
||||
import { api } from '@/api/client';
|
||||
import { Dashboard } from '@/pages/Dashboard';
|
||||
import { WhatIf } from '@/pages/WhatIf';
|
||||
|
||||
export function App() {
|
||||
const health = useQuery({ queryKey: ['health'], queryFn: api.health });
|
||||
|
|
@ -11,9 +12,15 @@ export function App() {
|
|||
<div className="min-h-screen flex flex-col">
|
||||
<header className="border-b border-slate-200 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<Link to="/" className="text-xl font-semibold tracking-tight">
|
||||
fire-planner
|
||||
</Link>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link to="/" className="text-xl font-semibold tracking-tight">
|
||||
fire-planner
|
||||
</Link>
|
||||
<nav className="flex gap-4 text-sm">
|
||||
<NavTab to="/">Dashboard</NavTab>
|
||||
<NavTab to="/what-if">What if</NavTab>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">
|
||||
api:{' '}
|
||||
<span className={health.data ? 'text-emerald-600' : 'text-amber-600'}>
|
||||
|
|
@ -29,8 +36,23 @@ export function App() {
|
|||
<main className="flex-1 max-w-7xl w-full mx-auto px-6 py-8">
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/what-if" element={<WhatIf />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NavTab({ to, children }: { to: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<NavLink
|
||||
to={to}
|
||||
end
|
||||
className={({ isActive }) =>
|
||||
`${isActive ? 'text-slate-900 font-medium' : 'text-slate-500 hover:text-slate-800'}`
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue