fixed rebalance ui, helper feature redistruvbtion
This commit is contained in:
@@ -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 <div className="muted">Loading…</div>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
<header className="space-y-1">
|
||||
<h2 className="text-lg font-semibold">Rebalance variable pool</h2>
|
||||
<h2 className="text-2xl font-semibold">Rebalance variable pool</h2>
|
||||
<p className="text-sm muted">
|
||||
Redistribute your current variable pool without changing future income percentages.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="card p-4 flex flex-wrap gap-4 items-center justify-between">
|
||||
<div className="text-sm">
|
||||
<div className="font-semibold">Available</div>
|
||||
<div className="text-xl font-mono">${(available / 100).toFixed(2)}</div>
|
||||
<div className="card glass p-5 flex flex-wrap gap-6 items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs uppercase tracking-wide muted">Available</div>
|
||||
<div className="text-2xl font-mono font-semibold">${(available / 100).toFixed(2)}</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div>Totals</div>
|
||||
<div className="font-mono">
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs uppercase tracking-wide muted">Totals</div>
|
||||
<div className="text-sm font-mono">
|
||||
${(total / 100).toFixed(2)} / ${(available / 100).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div>Savings total</div>
|
||||
<div className="font-mono">${(savingsTotal / 100).toFixed(2)}</div>
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs uppercase tracking-wide muted">Savings total</div>
|
||||
<div className="text-sm font-mono">${(savingsTotal / 100).toFixed(2)}</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
@@ -148,12 +159,12 @@ export default function RebalancePage() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="card p-4 space-y-3">
|
||||
<div className="flex flex-wrap gap-2 items-end">
|
||||
<div className="flex flex-col">
|
||||
<label className="text-xs muted">Adjust one category</label>
|
||||
<div className="card p-5 space-y-4">
|
||||
<div className="flex flex-wrap gap-3 items-end">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-xs uppercase tracking-wide muted">Adjust one category</label>
|
||||
<select
|
||||
className="input"
|
||||
className="input w-56"
|
||||
value={adjustId}
|
||||
onChange={(e) => setAdjustId(e.target.value)}
|
||||
>
|
||||
@@ -164,10 +175,10 @@ export default function RebalancePage() {
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label className="text-xs muted">Amount</label>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-xs uppercase tracking-wide muted">Amount</label>
|
||||
<input
|
||||
className="input"
|
||||
className="input w-40"
|
||||
type="number"
|
||||
min={0}
|
||||
step="0.01"
|
||||
@@ -176,32 +187,33 @@ export default function RebalancePage() {
|
||||
placeholder="0.00"
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="button" onClick={applyAdjustOne}>
|
||||
<button className="btn primary" type="button" onClick={applyAdjustOne}>
|
||||
Apply helper
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-auto">
|
||||
<div className="overflow-auto rounded-2xl border border-[--color-border]/50 bg-[--color-panel]">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<thead className="bg-[--color-surface]">
|
||||
<tr className="text-left">
|
||||
<th className="py-2">Category</th>
|
||||
<th className="py-2">Current</th>
|
||||
<th className="py-2">Percent</th>
|
||||
<th className="py-2">Target</th>
|
||||
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Category</th>
|
||||
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Current</th>
|
||||
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Percent</th>
|
||||
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="border-t border-[--color-panel]">
|
||||
<td className="py-2 font-medium">
|
||||
{row.name} {row.isSavings ? <span className="text-xs text-emerald-500">Savings</span> : null}
|
||||
<tr key={row.id} className="border-t border-[--color-border]/30">
|
||||
<td className="py-3 px-4 font-medium flex items-center gap-2">
|
||||
{row.name}
|
||||
{row.isSavings ? <span className="badge badge-ghost">Savings</span> : null}
|
||||
</td>
|
||||
<td className="py-2 font-mono">${(row.balanceCents / 100).toFixed(2)}</td>
|
||||
<td className="py-2">{row.percent}%</td>
|
||||
<td className="py-2">
|
||||
<td className="py-3 px-4 font-mono">${(row.balanceCents / 100).toFixed(2)}</td>
|
||||
<td className="py-3 px-4">{row.percent}%</td>
|
||||
<td className="py-3 px-4">
|
||||
<CurrencyInput
|
||||
className="w-32"
|
||||
className="input w-32"
|
||||
valueCents={row.targetCents}
|
||||
onChange={(cents) =>
|
||||
setRows((prev) =>
|
||||
@@ -238,7 +250,7 @@ export default function RebalancePage() {
|
||||
Apply rebalance
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
className="btn ghost"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
setForceSavings(false);
|
||||
|
||||
Reference in New Issue
Block a user