Files
GMW/services/frontend/src/components/charts/donut.tsx
T
asepharyana 1b56212d1a feat(frontend): rebuild as Ambient/WebGL console with all pages + command palette
Ground-up rombak UI: hapus semua component/page lama, bangun ulang dengan
desain sistem Ambient (WebGL haze + drifting motes, signal-driven color)
di atas kontrak API/WS/type yang sudah ada.

- Design system: globals.css tokens + primitives (glass, button, badge,
  select, avatar, toast, chart SVG murni).
- Shell: nav rail, topbar (status WS + pill signal + theme), AppFrame.
- 8 halaman: dashboard, voice (orbital stage), media, messages (live feed +
  detail AI), moderation, analysis (search), recordings, + chatbot floating.
- Command palette (Cmd/Ctrl+K) untuk navigasi cepat.
- Server fetch di-page di-try/catch agar render graceful saat backend mati.

Verified: tsc clean, next build 8/8 halaman, semua route 200.
2026-08-15 17:53:48 +07:00

52 lines
1.6 KiB
TypeScript

/** Stacked donut for moderation overview / composition. */
export function Donut({
segments,
size = 132,
thickness = 14,
centerLabel,
centerSub,
}: {
segments: { value: number; color: string; label: string }[];
size?: number;
thickness?: number;
centerLabel?: string;
centerSub?: string;
}) {
const total = segments.reduce((s, x) => s + x.value, 0) || 1;
const r = size / 2 - thickness / 2;
const c = 2 * Math.PI * r;
let offset = 0;
return (
<div className="relative inline-flex items-center justify-center" style={{ width: size, height: size }}>
<svg width={size} height={size} className="-rotate-90">
<circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--color-hairline)" strokeWidth={thickness} />
{segments.map((s, i) => {
const len = (s.value / total) * c;
const el = (
<circle
key={i}
cx={size / 2}
cy={size / 2}
r={r}
fill="none"
stroke={s.color}
strokeWidth={thickness}
strokeDasharray={`${len} ${c - len}`}
strokeDashoffset={-offset}
style={{ transition: "stroke-dashoffset 0.6s ease" }}
/>
);
offset += len;
return el;
})}
</svg>
{(centerLabel || centerSub) && (
<div className="absolute inset-0 flex flex-col items-center justify-center">
{centerLabel && <span className="display text-lg">{centerLabel}</span>}
{centerSub && <span className="mono text-[0.6rem] text-ink-faint">{centerSub}</span>}
</div>
)}
</div>
);
}