Files
beacon-web/src/features/stats/Segmented.tsx
T
MrAlders0n be072bb42c feat(stats): add Mesh + Observer stats tab with ECharts
Integrate the stats feature onto main: live mesh KPIs, observations
time-series, payload/node-type donuts, top-nodes/observers leaderboards,
radio presets, scope stats, and per-observer telemetry (battery, airtime,
noise, queue, receive errors) with 1h/6h/24h bucketing.

- adapt to the multi-IATA region model (useRegion -> single iata for /stats/*)
- rename stats getScopes -> getStatsScopes (avoids /scopes name-list collision)
- donut legend kept right but scrollable + truncating so it never clips
- balanced mesh grid; observer empty-state for telemetry-less observers
- normalize telemetry t to ms (raw path emits seconds, bucketed emits ms)
- add echarts 5.5.1; lazy-load StatsOverview chunk
2026-06-09 22:02:27 -04:00

51 lines
1.7 KiB
TypeScript

import type { ReactNode } from "react";
export interface SegmentedOption {
value: string;
label: string;
icon?: ReactNode;
}
interface SegmentedProps {
options: SegmentedOption[];
value: string;
onChange: (value: string) => void;
ariaLabel: string;
size?: "sm" | "md";
className?: string;
}
// A contained "pill group" segmented control bound to the palette: the active pill gets a primary
// tint + inset ring, inactive pills are muted. Used for the Stats sub-tabs (md, with icons) and the
// time-range selector (sm). Active state is conveyed with aria-pressed, not color alone.
export function Segmented({ options, value, onChange, ariaLabel, size = "sm", className }: SegmentedProps) {
const pad = size === "md" ? "px-3 py-1.5 text-xs" : "px-2.5 py-1 text-[11px]";
return (
<div
role="group"
aria-label={ariaLabel}
className={`inline-flex items-center gap-0.5 rounded-md border border-border bg-bg-raised p-0.5 ${className ?? ""}`}
>
{options.map((o) => {
const active = o.value === value;
return (
<button
key={o.value}
type="button"
aria-pressed={active}
onClick={() => onChange(o.value)}
className={`flex items-center gap-1.5 rounded font-mono font-semibold tracking-wide transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary ${pad} ${
active
? "bg-primary/15 text-text-bright ring-1 ring-inset ring-primary/30"
: "text-text-muted hover:text-text-normal"
}`}
>
{o.icon}
{o.label}
</button>
);
})}
</div>
);
}