104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
"use client";
|
||
|
||
import * as React from "react";
|
||
import { useNetWorth, useTransactions } from "@/api/hooks/reads";
|
||
import { Card } from "@/ds";
|
||
import { tugrikRaw } from "@/ds/money";
|
||
import { assetsStrings as s } from "./strings";
|
||
|
||
const mutedStyle: React.CSSProperties = { color: "var(--seed-color-fg-neutral-subtle)" };
|
||
|
||
export interface AccountDetailProps {
|
||
accountId: number;
|
||
}
|
||
|
||
/**
|
||
* Read-only account detail — no bank-server actions live here (that's iOS's
|
||
* `AccountDetailView`, gated behind a live bank connection this web app
|
||
* doesn't drive yet). Shows the balance from `/networth` plus this
|
||
* account's recent transactions.
|
||
*/
|
||
export function AccountDetail({ accountId }: AccountDetailProps) {
|
||
const netWorth = useNetWorth();
|
||
const transactions = useTransactions({ account: accountId, limit: 30 });
|
||
|
||
const account = (netWorth.data?.accounts ?? []).find((a) => a.accountId === accountId);
|
||
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
|
||
<header style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||
<BackLink />
|
||
<h1 style={{ margin: 0, fontSize: 18, fontWeight: 700, flex: 1 }}>{account?.bank ?? s.accountDetail.bank}</h1>
|
||
</header>
|
||
|
||
<Card style={{ textAlign: "center", padding: "24px 20px" }}>
|
||
<div style={{ fontSize: 12, ...mutedStyle }}>{account?.accountNumber}</div>
|
||
<div style={{ fontSize: 32, fontWeight: 700, marginTop: 8 }}>{tugrikRaw(account?.balance ?? "0")}</div>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "6px 20px" }}>
|
||
<DetailRow label={s.accountDetail.bank} value={account?.bank ?? "—"} />
|
||
<Divider />
|
||
<DetailRow label={s.accountDetail.accountNumber} value={account?.accountNumber ?? "—"} />
|
||
<Divider />
|
||
<DetailRow label={s.accountDetail.currency} value={account?.currency ?? "—"} />
|
||
</Card>
|
||
|
||
<section>
|
||
<h3 style={{ margin: "0 0 10px", fontSize: 14, fontWeight: 700 }}>{s.accountDetail.recentTransactions}</h3>
|
||
{transactions.isLoading ? (
|
||
<p style={{ fontSize: 13, ...mutedStyle }}>…</p>
|
||
) : (transactions.data ?? []).length === 0 ? (
|
||
<p style={{ fontSize: 13, ...mutedStyle }}>{s.accountDetail.noTransactions}</p>
|
||
) : (
|
||
<Card style={{ padding: 0 }}>
|
||
{(transactions.data ?? []).map((txn, i) => {
|
||
const income = txn.direction === "income";
|
||
return (
|
||
<React.Fragment key={i}>
|
||
{i > 0 && <Divider />}
|
||
<div style={{ display: "flex", justifyContent: "space-between", padding: "14px 16px" }}>
|
||
<div>
|
||
<div style={{ fontWeight: 600 }}>{txn.title || txn.category}</div>
|
||
<div style={{ fontSize: 12, ...mutedStyle }}>{txn.date.slice(0, 10)}</div>
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontWeight: 700,
|
||
color: income ? "var(--seed-color-fg-positive)" : "var(--seed-color-fg-critical)",
|
||
}}
|
||
>
|
||
{income ? "+" : "−"}
|
||
{tugrikRaw(txn.amount)}
|
||
</div>
|
||
</div>
|
||
</React.Fragment>
|
||
);
|
||
})}
|
||
</Card>
|
||
)}
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function BackLink() {
|
||
return (
|
||
<a href="/assets" style={{ textDecoration: "none", color: "inherit", fontWeight: 600 }}>
|
||
← {s.common.back}
|
||
</a>
|
||
);
|
||
}
|
||
|
||
function DetailRow({ label, value }: { label: string; value: string }) {
|
||
return (
|
||
<div style={{ display: "flex", justifyContent: "space-between", padding: "14px 0" }}>
|
||
<span style={mutedStyle}>{label}</span>
|
||
<span style={{ fontWeight: 600 }}>{value}</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Divider() {
|
||
return <div style={{ height: 1, background: "var(--seed-color-border-neutral, #e5e5e5)" }} />;
|
||
}
|