diff --git a/web/src/pages/settings/RebalancePage.tsx b/web/src/pages/settings/RebalancePage.tsx index 8bd0846..131aa1e 100644 --- a/web/src/pages/settings/RebalancePage.tsx +++ b/web/src/pages/settings/RebalancePage.tsx @@ -71,22 +71,33 @@ export default function RebalancePage() { push("err", "Amount exceeds available budget"); return; } - const baseSum = sum(others.map((o) => o.targetCents)) || others.length; - const next = rows.map((r) => ({ ...r })); - let distributed = 0; - others.forEach((o) => { - const share = Math.floor((remaining * (o.targetCents || 1)) / baseSum); - const idx = next.findIndex((n) => n.id === o.id); - next[idx].targetCents = share; - distributed += share; + + const weightSum = others.reduce((s, o) => s + (o.percent || 1), 0) || others.length; + const provisional = others.map((o) => { + const exact = (remaining * (o.percent || 1)) / weightSum; + const base = Math.floor(exact); + return { id: o.id, base, frac: exact - base }; + }); + let distributed = provisional.reduce((s, p) => s + p.base, 0); + let leftover = remaining - distributed; + provisional + .slice() + .sort((a, b) => b.frac - a.frac) + .forEach((p) => { + if (leftover > 0) { + p.base += 1; + leftover -= 1; + } + }); + + const next = rows.map((r) => ({ ...r })); + provisional.forEach((p) => { + const idx = next.findIndex((n) => n.id === p.id); + if (idx >= 0) next[idx].targetCents = p.base; }); - const leftover = remaining - distributed; - if (leftover > 0) { - const firstIdx = next.findIndex((n) => n.id !== adjustId); - if (firstIdx >= 0) next[firstIdx].targetCents += leftover; - } const targetIdx = next.findIndex((n) => n.id === adjustId); - next[targetIdx].targetCents = desired; + if (targetIdx >= 0) next[targetIdx].targetCents = desired; + setRows(next); }; @@ -115,28 +126,28 @@ export default function RebalancePage() { if (isLoading || !data) return
Loading…
; return ( -
+
-

Rebalance variable pool

+

Rebalance variable pool

Redistribute your current variable pool without changing future income percentages.

-
-
-
Available
-
${(available / 100).toFixed(2)}
+
+
+
Available
+
${(available / 100).toFixed(2)}
-
-
Totals
-
+
+
Totals
+
${(total / 100).toFixed(2)} / ${(available / 100).toFixed(2)}
-
-
Savings total
-
${(savingsTotal / 100).toFixed(2)}
+
+
Savings total
+
${(savingsTotal / 100).toFixed(2)}
-
-
-
- +
+
+
+
-
- +
+
-
-
+
- + - - - - + + + + {rows.map((row) => ( - - + - - - + +
CategoryCurrentPercentTargetCategoryCurrentPercentTarget
- {row.name} {row.isSavings ? Savings : null} +
+ {row.name} + {row.isSavings ? Savings : null} ${(row.balanceCents / 100).toFixed(2)}{row.percent}% + ${(row.balanceCents / 100).toFixed(2)}{row.percent}% setRows((prev) => @@ -238,7 +250,7 @@ export default function RebalancePage() { Apply rebalance