0.2.34: manage exited status from the Capital Accounts view

The eNAV keeps listing exited members each quarter, so the admin needs
the exit control where the statements live:
- Capital Accounts table gains a Status column with the same
  Exited-badge / mark / undo flow as the Partners tab (keyed per
  investor+fund pair — marking any statement row marks them all)
- set_partner_exited now creates the access row (flag set) when a
  manually-entered investor has statements but no roster entry yet;
  clearing a never-set exit stays 404

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jonathan Kirkwood
2026-07-03 09:03:49 -05:00
co-authored by Claude Opus 4.8
parent a639ba14cf
commit 6313d781b4
8 changed files with 115 additions and 15 deletions
+61 -2
View File
@@ -7,6 +7,10 @@ export default function CapitalAccounts() {
const [users, setUsers] = useState<User[]>([]);
const [rows, setRows] = useState<CapitalAccount[]>([]);
const [error, setError] = useState("");
// Exit status is per investor+fund (from the eNAV the admin keeps re-importing), so the
// inline picker is keyed on that pair — marking any statement row marks them all.
const [exitingKey, setExitingKey] = useState<string | null>(null);
const [exitDate, setExitDate] = useState(() => new Date().toISOString().slice(0, 10));
const investors = useMemo(() => users.filter((u) => u.role === "investor"), [users]);
const userById = useMemo(() => new Map(users.map((u) => [u.id, u])), [users]);
@@ -15,6 +19,17 @@ export default function CapitalAccounts() {
const load = () => {
api.listCapitalAccounts().then(setRows).catch((e) => setError(e.message));
};
const saveExit = async (r: CapitalAccount, exitedOn: string | null) => {
setError("");
try {
await api.setPartnerExited(r.entity_id, r.investor_user_id, exitedOn);
setExitingKey(null);
load();
} catch (e: any) {
setError(e.message || "Failed to update exit status");
}
};
useEffect(() => {
api.listEntities().then(setEntities).catch(() => {});
api.listUsers().then(setUsers).catch(() => {});
@@ -48,13 +63,14 @@ export default function CapitalAccounts() {
<th className="px-4 py-2 font-medium text-right">Contributions</th>
<th className="px-4 py-2 font-medium text-right">Distributions</th>
<th className="px-4 py-2 font-medium text-right">Ending balance</th>
<th className="px-4 py-2 font-medium">Status</th>
<th className="px-4 py-2"></th>
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.id} className="border-t border-gray-100">
<td className="px-4 py-2 text-gray-900">
<td className={`px-4 py-2 ${r.exited_on ? "text-gray-500" : "text-gray-900"}`}>
{userById.get(r.investor_user_id)?.name ?? r.investor_user_id}
</td>
<td className="px-4 py-2 text-gray-600">{entityById.get(r.entity_id)?.name ?? r.entity_id}</td>
@@ -64,6 +80,49 @@ export default function CapitalAccounts() {
<td className="px-4 py-2 text-right text-gray-900 font-medium">
{formatMoneyExact(r.ending_balance_cents)}
</td>
<td className="px-4 py-2 whitespace-nowrap">
{r.exited_on ? (
<span>
<span className="text-xs font-medium uppercase text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
Exited {formatDate(r.exited_on)}
</span>
<button
onClick={() => saveExit(r, null)}
className="ml-2 text-xs text-accent-600 hover:text-accent-700"
>
Undo
</button>
</span>
) : exitingKey === `${r.entity_id}:${r.investor_user_id}` ? (
<span className="flex items-center gap-1.5">
<input
type="date"
value={exitDate}
onChange={(e) => setExitDate(e.target.value)}
className="px-1.5 py-0.5 border border-gray-300 rounded text-xs"
/>
<button
onClick={() => exitDate && saveExit(r, exitDate)}
className="text-xs text-accent-600 hover:text-accent-700 font-medium"
>
Save
</button>
<button
onClick={() => setExitingKey(null)}
className="text-xs text-gray-400 hover:text-gray-600"
>
Cancel
</button>
</span>
) : (
<button
onClick={() => setExitingKey(`${r.entity_id}:${r.investor_user_id}`)}
className="text-xs text-gray-400 hover:text-gray-600"
>
Mark exited
</button>
)}
</td>
<td className="px-4 py-2 text-right">
<button
onClick={() => {
@@ -79,7 +138,7 @@ export default function CapitalAccounts() {
))}
{rows.length === 0 && (
<tr>
<td colSpan={7} className="px-4 py-6 text-center text-gray-400">
<td colSpan={8} className="px-4 py-6 text-center text-gray-400">
No statements yet.
</td>
</tr>