"use client"; import * as React from "react"; import Link from "next/link"; import { BottomSheetRoot, BottomSheetBackdrop, BottomSheetPositioner, BottomSheetContent, BottomSheetHeader, BottomSheetTitle, BottomSheetBody, BottomSheetFooter, TextFieldRoot, TextFieldInput, Skeleton, } from "@seed-design/react"; import { useNetWorth, useManualAssets, useLending } from "@/api/hooks/reads"; import { useManualAssetMutations, useLendingMutations } from "@/api/hooks/mutations"; import { Card, HideAmountsToggle, MercuryButton, IconChip, SectionHeader, EmptyState, Icon, type IconName } from "@/ds"; import { tugrik, tugrikRaw } from "@/ds/money"; import type { Account, ManualAsset, Lending } from "@/api/schemas"; import { assetsStrings as s } from "./strings"; import { useHideAmountsTick } from "./useHideAmountsTick"; import { ConfirmDialog } from "./ConfirmDialog"; const mutedStyle: React.CSSProperties = { color: "var(--seed-color-fg-neutral-subtle)" }; const rowStyle: React.CSSProperties = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "14px 16px", }; const titleStyle: React.CSSProperties = { fontSize: 15, fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }; const subtitleStyle: React.CSSProperties = { fontSize: 12, ...mutedStyle }; const amountStyle: React.CSSProperties = { fontSize: 15, fontWeight: 700, flexShrink: 0 }; // Bank/lending/asset chip tints — soft tones matching the categoryStyle // palette used elsewhere in Mercury (transactions, planner). const BANK_TINT = { tint: "#DDEAF6", fg: "#215C9A" }; const LENDING_TINT = { tint: "#E3F0D8", fg: "#3F7A1E" }; function assetChip(category: string): { icon: IconName; tint: string; fg: string } { switch (category) { case "car": return { icon: "car", tint: "#DDE6F6", fg: "#2A4B9A" }; case "electronics": return { icon: "monitor", tint: "#E1E5EA", fg: "#42505F" }; default: return { icon: "layers", tint: "#EDEBE7", fg: "#6B6257" }; } } export function AssetsView() { // Re-renders when the global hide-amounts flag flips (tugrik()/tugrikRaw() // read it from localStorage synchronously, so this is how the numbers on // this page react to the header toggle without a reload). useHideAmountsTick(); const netWorth = useNetWorth(); const manualAssets = useManualAssets(); const lending = useLending(); const assetMutations = useManualAssetMutations(); const lendingMutations = useLendingMutations(); const [addAssetOpen, setAddAssetOpen] = React.useState(false); const [addLoanOpen, setAddLoanOpen] = React.useState(false); const [deleteAssetName, setDeleteAssetName] = React.useState(null); const [revalueAssetName, setRevalueAssetName] = React.useState(null); const [deleteLoan, setDeleteLoan] = React.useState(null); const accounts: Account[] = netWorth.data?.accounts ?? []; const assets: ManualAsset[] = manualAssets.data ?? []; const loans: Lending[] = lending.data ?? []; return (

{s.header.title}

{netWorth.isLoading ? ( ) : accounts.length === 0 ? ( ) : ( {accounts.map((account, i) => ( {i > 0 && }
{account.bank} {account.accountNumber}
{tugrik(account.balance)}
))}
)}
setAddAssetOpen(true)} actionLabel={s.manualAssets.add} /> {manualAssets.isLoading ? ( ) : assets.length === 0 ? ( ) : (
{assets.map((asset) => ( setRevalueAssetName(asset.name)} onDelete={() => setDeleteAssetName(asset.name)} /> ))}
)}
setAddLoanOpen(true)} actionLabel={s.lending.add} /> {lending.isLoading ? ( ) : loans.length === 0 ? ( ) : ( {loans.map((loan, i) => ( {i > 0 && } setDeleteLoan(loan)} /> ))} )}
{addAssetOpen && ( setAddAssetOpen(false)} saving={assetMutations.add.isPending} onSave={async (values) => { await assetMutations.add.mutateAsync(values); setAddAssetOpen(false); }} /> )} {addLoanOpen && ( setAddLoanOpen(false)} saving={lendingMutations.create.isPending} onSave={async (values) => { await lendingMutations.create.mutateAsync(values); setAddLoanOpen(false); }} /> )} setDeleteAssetName(null)} onConfirm={async () => { if (!deleteAssetName) return; await assetMutations.delete.mutateAsync(deleteAssetName); setDeleteAssetName(null); }} /> setRevalueAssetName(null)} onConfirm={async () => { const name = revalueAssetName; if (!name) return; setRevalueAssetName(null); await assetMutations.revalue.mutateAsync(name); }} /> setDeleteLoan(null)} onConfirm={async () => { if (!deleteLoan) return; await lendingMutations.delete.mutateAsync(deleteLoan.id); setDeleteLoan(null); }} />
); } // --- Net worth header ------------------------------------------------------- function NetWorthCard({ netWorth, loading }: { netWorth?: { total: string; assets: string; liabilities: string }; loading: boolean }) { return ( {loading ? ( ) : ( <>
{s.netWorth.total}
{tugrik(netWorth?.total ?? "0")}
{s.netWorth.assets}
{tugrik(netWorth?.assets ?? "0")}
{s.netWorth.liabilities}
{tugrik(netWorth?.liabilities ?? "0")}
)}
); } // --- Small shared bits ------------------------------------------------------- function SkeletonRows({ count }: { count: number }) { return ( {Array.from({ length: count }).map((_, i) => ( ))} ); } function Divider() { return
; } // --- Manual asset row --------------------------------------------------- function ManualAssetRow({ asset, revaluing, onRevalue, onDelete, }: { asset: ManualAsset; revaluing: boolean; onRevalue: () => void; onDelete: () => void; }) { const change = Number(asset.change || "0"); const positive = change >= 0; const categoryLabel = s.manualAssets.categories[asset.category] ?? asset.category; const conditionLabel = s.manualAssets.conditions[asset.condition] ?? asset.condition; const chip = assetChip(asset.category); return (
{asset.name} {categoryLabel} · {conditionLabel}
{tugrikRaw(asset.value)}
{positive ? "+" : "−"} {tugrikRaw(Math.abs(change))}
); } // --- Lending row --------------------------------------------------------- function statusLabel(loan: Lending): string { if (loan.overdue) return s.lending.statusOverdue; switch (loan.status) { case "repaid": return s.lending.statusPaid; case "partial": return s.lending.statusPartial; default: return s.lending.statusUnpaid; } } function LoanRow({ loan, onDelete }: { loan: Lending; onDelete: () => void }) { return (
{loan.person} {statusLabel(loan)}
{tugrikRaw(loan.remaining)}
/ {tugrikRaw(loan.principal)}
); } // --- Add manual asset sheet ------------------------------------------------ interface NewAssetValues { name: string; category: string; value: string; acquiredValue: string; condition: string; } function AddManualAssetSheet({ onClose, onSave, saving, }: { onClose: () => void; onSave: (values: NewAssetValues) => void | Promise; saving: boolean; }) { const [name, setName] = React.useState(""); const [category, setCategory] = React.useState("car"); const [condition, setCondition] = React.useState("used"); const [price, setPrice] = React.useState(""); const canSave = name.trim().length > 0 && Number(price) > 0 && !saving; return ( { if (!next) onClose(); }}> {s.manualAssets.add} {s.common.cancel} onSave({ name: name.trim(), category, value: price, acquiredValue: price, condition, }) } > {s.common.save} ); } // --- Add loan sheet --------------------------------------------------------- interface NewLoanValues { person: string; amount: string; lentOn: string; dueOn?: string; note?: string; } function todayISO(): string { return new Date().toISOString().slice(0, 10); } function AddLoanSheet({ onClose, onSave, saving, }: { onClose: () => void; onSave: (values: NewLoanValues) => void | Promise; saving: boolean; }) { const [person, setPerson] = React.useState(""); const [amount, setAmount] = React.useState(""); const [lentOn, setLentOn] = React.useState(todayISO()); const [dueOn, setDueOn] = React.useState(""); const [note, setNote] = React.useState(""); const canSave = person.trim().length > 0 && Number(amount) > 0 && !saving; return ( { if (!next) onClose(); }}> {s.lending.add} {s.common.cancel} onSave({ person: person.trim(), amount, lentOn, dueOn: dueOn || undefined, note: note.trim() || undefined, }) } > {s.common.save} ); }