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.
52 lines
1.6 KiB
TypeScript
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>
|
|
);
|
|
}
|