0.2.35: exited positions show history clipped at the exit date
The eNAV keeps producing statements after an exit (quarterly for funds, event-driven for SPVs), so an unfiltered graph would crash to zero. Now the exited card gets its collapsible History back — chart + table showing the capital journey up to exited_on only. Post-exit statements stay recorded (audit trail; Undo restores full history) but are never plotted. History section extracted into a shared HistorySection component; toggle counts "statements" not "quarters" (SPV statements are event-driven). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6313d781b4
commit
2c87ab499a
@@ -3,7 +3,7 @@
|
||||
// - content-hashed /assets/* are cache-first (immutable, safe forever)
|
||||
// - /api/* is never cached
|
||||
// Bump CACHE on each release so old entries are purged.
|
||||
const CACHE = 'ten31-portal-0.2.34'
|
||||
const CACHE = 'ten31-portal-0.2.35'
|
||||
|
||||
self.addEventListener('install', () => self.skipWaiting())
|
||||
|
||||
|
||||
@@ -256,18 +256,13 @@ function CapitalBlock({
|
||||
() => [...accounts].sort((a, b) => a.as_of_date.localeCompare(b.as_of_date)),
|
||||
[accounts],
|
||||
);
|
||||
const chartPoints: CapitalPoint[] = history.map((a) => ({
|
||||
date: a.as_of_date,
|
||||
value: a.ending_balance_cents,
|
||||
paidIn: a.contributions_cents,
|
||||
distributions: a.distributions_cents,
|
||||
}));
|
||||
// Collapsed by default so the portal opens clean; the investor expands the trend per fund.
|
||||
const [showChart, setShowChart] = useState(false);
|
||||
|
||||
// A sold/transferred stake: the fund's books show $0 with no distribution, which is not a
|
||||
// loss. Show a quiet Exited badge and keep the documents; no balance, no gain/loss.
|
||||
// loss. Show a quiet Exited badge and their capital journey UP TO the exit — statements
|
||||
// the eNAV keeps producing after that date are recorded but never plotted, so the line
|
||||
// ends at the exit instead of crashing to zero.
|
||||
if (latest.exited_on) {
|
||||
const clipped = history.filter((a) => a.as_of_date <= latest.exited_on!);
|
||||
return (
|
||||
<div className={divider ? "mt-6 pt-5 border-t border-gray-100" : "mt-3"}>
|
||||
{label && <p className="text-sm font-medium text-gray-700">{label}</p>}
|
||||
@@ -279,6 +274,7 @@ function CapitalBlock({
|
||||
<p className="text-sm text-gray-400 mt-2">
|
||||
This position was sold or transferred. Documents remain available below.
|
||||
</p>
|
||||
<HistorySection history={clipped} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -314,22 +310,41 @@ function CapitalBlock({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{history.length > 1 && (
|
||||
<div className="mt-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowChart((v) => !v)}
|
||||
aria-expanded={showChart}
|
||||
className="flex items-center gap-1.5 text-xs font-medium text-gray-500 uppercase hover:text-gray-700"
|
||||
>
|
||||
<span className={`transition-transform ${showChart ? "rotate-90" : ""}`}>▸</span>
|
||||
History
|
||||
<span className="normal-case font-normal text-gray-400">
|
||||
· {history.length} quarters
|
||||
</span>
|
||||
</button>
|
||||
{showChart && (
|
||||
<div className="mt-3">
|
||||
<HistorySection history={history} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Collapsible capital-over-time chart + statement table. Collapsed by default so the portal
|
||||
// opens clean; the investor expands the trend per fund. SPVs get sparse event-driven points,
|
||||
// funds quarterly ones — the chart plots whatever statements exist.
|
||||
function HistorySection({ history }: { history: CapitalAccount[] }) {
|
||||
const [showChart, setShowChart] = useState(false);
|
||||
if (history.length < 2) return null;
|
||||
|
||||
const chartPoints: CapitalPoint[] = history.map((a) => ({
|
||||
date: a.as_of_date,
|
||||
value: a.ending_balance_cents,
|
||||
paidIn: a.contributions_cents,
|
||||
distributions: a.distributions_cents,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowChart((v) => !v)}
|
||||
aria-expanded={showChart}
|
||||
className="flex items-center gap-1.5 text-xs font-medium text-gray-500 uppercase hover:text-gray-700"
|
||||
>
|
||||
<span className={`transition-transform ${showChart ? "rotate-90" : ""}`}>▸</span>
|
||||
History
|
||||
<span className="normal-case font-normal text-gray-400">
|
||||
· {history.length} statements
|
||||
</span>
|
||||
</button>
|
||||
{showChart && (
|
||||
<div className="mt-3">
|
||||
<CapitalChart points={chartPoints} />
|
||||
<table className="w-full text-sm mt-4">
|
||||
<thead className="text-gray-400 text-left">
|
||||
@@ -351,8 +366,6 @@ function CapitalBlock({
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Bumped each release so the running build is visible in the UI.
|
||||
// If the number shown in the app doesn't match the installed s9pk version,
|
||||
// the new frontend isn't actually being served.
|
||||
export const APP_VERSION = "0.2.34";
|
||||
export const APP_VERSION = "0.2.35";
|
||||
|
||||
Reference in New Issue
Block a user