"use client"; interface Props { bucketLabels: string[]; shares: number[]; // длина = bucketLabels.length, сумма ≈ 100 recommended: number[]; // оригинальные share_pct из API onChange: (next: number[]) => void; onReset: () => void; } /** * Auto-rescale slider group. Когда пользователь двигает один слайдер до X%, * остальные пересчитываются пропорционально к (100 - X)%, сохраняя их * относительные доли. Если у других нет массы (sum=0) — распределяем поровну. */ export function RecommendShareSliders({ bucketLabels, shares, recommended, onChange, onReset, }: Props) { const handle = (idx: number, raw: number) => { const v = Math.max(0, Math.min(100, raw)); const next = [...shares]; next[idx] = v; const remaining = 100 - v; const others = shares.filter((_, i) => i !== idx); const otherSum = others.reduce((a, b) => a + b, 0); if (otherSum <= 0) { const each = remaining / others.length; for (let i = 0; i < next.length; i++) { if (i !== idx) next[i] = each; } } else { for (let i = 0; i < next.length; i++) { if (i !== idx) next[i] = (shares[i] * remaining) / otherSum; } } // round to 1 decimal, fix sum-rounding drift on the changed slider const rounded = next.map((x) => Math.round(x * 10) / 10); const drift = 100 - rounded.reduce((a, b) => a + b, 0); rounded[idx] = Math.round((rounded[idx] + drift) * 10) / 10; onChange(rounded); }; const dirty = shares.some( (s, i) => Math.abs(s - (recommended[i] ?? 0)) > 0.05, ); return (
Сумма: {shares.reduce((a, b) => a + b, 0).toFixed(1)} %
{bucketLabels.map((label, i) => { const recValue = recommended[i] ?? 0; const v = shares[i] ?? 0; const delta = v - recValue; return (
{label} handle(i, Number(e.target.value))} style={{ width: "100%" }} />
{v.toFixed(1)}% {Math.abs(delta) > 0.1 ? ( 0 ? "#0a7a3a" : "#b3261e", }} > {delta > 0 ? "+" : ""} {delta.toFixed(1)} ) : null}
); })}
); }