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");
|
push("err", "Amount exceeds available budget");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const baseSum = sum(others.map((o) => o.targetCents)) || others.length;
|
|
||||||
const next = rows.map((r) => ({ ...r }));
|
const weightSum = others.reduce((s, o) => s + (o.percent || 1), 0) || others.length;
|
||||||
let distributed = 0;
|
const provisional = others.map((o) => {
|
||||||
others.forEach((o) => {
|
const exact = (remaining * (o.percent || 1)) / weightSum;
|
||||||
const share = Math.floor((remaining * (o.targetCents || 1)) / baseSum);
|
const base = Math.floor(exact);
|
||||||
const idx = next.findIndex((n) => n.id === o.id);
|
return { id: o.id, base, frac: exact - base };
|
||||||
next[idx].targetCents = share;
|
});
|
||||||
distributed += share;
|
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);
|
const targetIdx = next.findIndex((n) => n.id === adjustId);
|
||||||
next[targetIdx].targetCents = desired;
|
if (targetIdx >= 0) next[targetIdx].targetCents = desired;
|
||||||
|
|
||||||
setRows(next);
|
setRows(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,28 +126,28 @@ export default function RebalancePage() {
|
|||||||
if (isLoading || !data) return <div className="muted">Loading…</div>;
|
if (isLoading || !data) return <div className="muted">Loading…</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
<header className="space-y-1">
|
<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">
|
<p className="text-sm muted">
|
||||||
Redistribute your current variable pool without changing future income percentages.
|
Redistribute your current variable pool without changing future income percentages.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="card p-4 flex flex-wrap gap-4 items-center justify-between">
|
<div className="card glass p-5 flex flex-wrap gap-6 items-center justify-between">
|
||||||
<div className="text-sm">
|
<div className="space-y-1">
|
||||||
<div className="font-semibold">Available</div>
|
<div className="text-xs uppercase tracking-wide muted">Available</div>
|
||||||
<div className="text-xl font-mono">${(available / 100).toFixed(2)}</div>
|
<div className="text-2xl font-mono font-semibold">${(available / 100).toFixed(2)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm">
|
<div className="space-y-1">
|
||||||
<div>Totals</div>
|
<div className="text-xs uppercase tracking-wide muted">Totals</div>
|
||||||
<div className="font-mono">
|
<div className="text-sm font-mono">
|
||||||
${(total / 100).toFixed(2)} / ${(available / 100).toFixed(2)}
|
${(total / 100).toFixed(2)} / ${(available / 100).toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm">
|
<div className="space-y-1">
|
||||||
<div>Savings total</div>
|
<div className="text-xs uppercase tracking-wide muted">Savings total</div>
|
||||||
<div className="font-mono">${(savingsTotal / 100).toFixed(2)}</div>
|
<div className="text-sm font-mono">${(savingsTotal / 100).toFixed(2)}</div>
|
||||||
</div>
|
</div>
|
||||||
<label className="flex items-center gap-2 text-sm">
|
<label className="flex items-center gap-2 text-sm">
|
||||||
<input
|
<input
|
||||||
@@ -148,12 +159,12 @@ export default function RebalancePage() {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card p-4 space-y-3">
|
<div className="card p-5 space-y-4">
|
||||||
<div className="flex flex-wrap gap-2 items-end">
|
<div className="flex flex-wrap gap-3 items-end">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-xs muted">Adjust one category</label>
|
<label className="text-xs uppercase tracking-wide muted">Adjust one category</label>
|
||||||
<select
|
<select
|
||||||
className="input"
|
className="input w-56"
|
||||||
value={adjustId}
|
value={adjustId}
|
||||||
onChange={(e) => setAdjustId(e.target.value)}
|
onChange={(e) => setAdjustId(e.target.value)}
|
||||||
>
|
>
|
||||||
@@ -164,10 +175,10 @@ export default function RebalancePage() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-xs muted">Amount</label>
|
<label className="text-xs uppercase tracking-wide muted">Amount</label>
|
||||||
<input
|
<input
|
||||||
className="input"
|
className="input w-40"
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
step="0.01"
|
step="0.01"
|
||||||
@@ -176,32 +187,33 @@ export default function RebalancePage() {
|
|||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn" type="button" onClick={applyAdjustOne}>
|
<button className="btn primary" type="button" onClick={applyAdjustOne}>
|
||||||
Apply helper
|
Apply helper
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead className="bg-[--color-surface]">
|
||||||
<tr className="text-left">
|
<tr className="text-left">
|
||||||
<th className="py-2">Category</th>
|
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Category</th>
|
||||||
<th className="py-2">Current</th>
|
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Current</th>
|
||||||
<th className="py-2">Percent</th>
|
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Percent</th>
|
||||||
<th className="py-2">Target</th>
|
<th className="py-3 px-4 text-xs uppercase tracking-wide muted">Target</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows.map((row) => (
|
{rows.map((row) => (
|
||||||
<tr key={row.id} className="border-t border-[--color-panel]">
|
<tr key={row.id} className="border-t border-[--color-border]/30">
|
||||||
<td className="py-2 font-medium">
|
<td className="py-3 px-4 font-medium flex items-center gap-2">
|
||||||
{row.name} {row.isSavings ? <span className="text-xs text-emerald-500">Savings</span> : null}
|
{row.name}
|
||||||
|
{row.isSavings ? <span className="badge badge-ghost">Savings</span> : null}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 font-mono">${(row.balanceCents / 100).toFixed(2)}</td>
|
<td className="py-3 px-4 font-mono">${(row.balanceCents / 100).toFixed(2)}</td>
|
||||||
<td className="py-2">{row.percent}%</td>
|
<td className="py-3 px-4">{row.percent}%</td>
|
||||||
<td className="py-2">
|
<td className="py-3 px-4">
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
className="w-32"
|
className="input w-32"
|
||||||
valueCents={row.targetCents}
|
valueCents={row.targetCents}
|
||||||
onChange={(cents) =>
|
onChange={(cents) =>
|
||||||
setRows((prev) =>
|
setRows((prev) =>
|
||||||
@@ -238,7 +250,7 @@ export default function RebalancePage() {
|
|||||||
Apply rebalance
|
Apply rebalance
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn"
|
className="btn ghost"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setForceSavings(false);
|
setForceSavings(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user