feat(web): rework assets visuals to reference bar

This commit is contained in:
Munkherdene 2026-08-22 22:16:05 +08:00
parent e0c07be232
commit 43108a92bb
6 changed files with 236 additions and 152 deletions

View file

@ -2,9 +2,12 @@
import * as React from "react";
import { useNetWorth, useTransactions } from "@/api/hooks/reads";
import { Card } from "@/ds";
import { Card, IconChip, EmptyState } from "@/ds";
import { categoryStyle } from "@/ds/categoryStyle";
import { tugrikRaw } from "@/ds/money";
import type { Txn } from "@/api/schemas";
import { assetsStrings as s } from "./strings";
import { DetailHeader } from "./DetailHeader";
const mutedStyle: React.CSSProperties = { color: "var(--seed-color-fg-neutral-subtle)" };
@ -26,10 +29,7 @@ export function AccountDetail({ accountId }: AccountDetailProps) {
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>
<DetailHeader title={account?.bank ?? s.accountDetail.bank} />
<Card style={{ textAlign: "center", padding: "24px 20px" }}>
<div style={{ fontSize: 12, ...mutedStyle }}>{account?.accountNumber}</div>
@ -44,37 +44,22 @@ export function AccountDetail({ accountId }: AccountDetailProps) {
<DetailRow label={s.accountDetail.currency} value={account?.currency ?? "—"} />
</Card>
<section>
<h3 style={{ margin: "0 0 10px", fontSize: 14, fontWeight: 700 }}>{s.accountDetail.recentTransactions}</h3>
<section style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<h3 style={{ margin: 0, 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 }}>
<EmptyState icon="receipt" title={s.accountDetail.noTransactions} compact />
</Card>
) : (
<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>
);
})}
{(transactions.data ?? []).map((txn, i) => (
<React.Fragment key={i}>
{i > 0 && <Divider />}
<TxnRow txn={txn} />
</React.Fragment>
))}
</Card>
)}
</section>
@ -82,11 +67,32 @@ export function AccountDetail({ accountId }: AccountDetailProps) {
);
}
function BackLink() {
function TxnRow({ txn }: { txn: Txn }) {
const income = txn.direction === "income";
const cat = categoryStyle(txn.category, income);
return (
<a href="/assets" style={{ textDecoration: "none", color: "inherit", fontWeight: 600 }}>
{s.common.back}
</a>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "14px 16px" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon={cat.icon} tint={cat.tint} fg={cat.fg} />
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span style={{ fontSize: 15, fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{txn.title || cat.name}
</span>
<span style={{ fontSize: 12, ...mutedStyle }}>{txn.date.slice(0, 10)}</span>
</div>
</div>
<span
style={{
fontSize: 15,
fontWeight: 700,
flexShrink: 0,
color: income ? "var(--seed-color-fg-positive)" : "var(--seed-color-fg-critical)",
}}
>
{income ? "+" : ""}
{tugrikRaw(txn.amount)}
</span>
</div>
);
}

View file

@ -17,7 +17,7 @@ import {
} from "@seed-design/react";
import { useNetWorth, useManualAssets, useLending } from "@/api/hooks/reads";
import { useManualAssetMutations, useLendingMutations } from "@/api/hooks/mutations";
import { Card, HideAmountsToggle, MercuryButton } from "@/ds";
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";
@ -32,6 +32,31 @@ const rowStyle: React.CSSProperties = {
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()
@ -65,11 +90,13 @@ export function AssetsView() {
<NetWorthCard netWorth={netWorth.data} loading={netWorth.isLoading} />
<section style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<SectionTitle>{s.accounts.title}</SectionTitle>
<SectionHeader title={s.accounts.title} />
{netWorth.isLoading ? (
<SkeletonRows count={2} />
) : accounts.length === 0 ? (
<EmptyText>{s.accounts.empty}</EmptyText>
<Card style={{ padding: 0 }}>
<EmptyState icon="bank" title={s.accounts.empty} hint={s.accounts.emptyHint} />
</Card>
) : (
<Card style={{ padding: 0 }}>
{accounts.map((account, i) => (
@ -79,11 +106,14 @@ export function AssetsView() {
href={`/assets/account/${account.accountId}`}
style={{ ...rowStyle, color: "inherit", textDecoration: "none" }}
>
<div>
<div style={{ fontWeight: 600 }}>{account.bank}</div>
<div style={{ fontSize: 12, ...mutedStyle }}>{account.accountNumber}</div>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon="bank" {...BANK_TINT} />
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span style={titleStyle}>{account.bank}</span>
<span style={subtitleStyle}>{account.accountNumber}</span>
</div>
</div>
<div style={{ fontWeight: 700 }}>{tugrik(account.balance)}</div>
<span style={amountStyle}>{tugrik(account.balance)}</span>
</Link>
</React.Fragment>
))}
@ -92,11 +122,13 @@ export function AssetsView() {
</section>
<section style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<SectionHeaderRow title={s.manualAssets.title} onAdd={() => setAddAssetOpen(true)} addLabel={s.manualAssets.add} />
<SectionHeader title={s.manualAssets.title} onAction={() => setAddAssetOpen(true)} actionLabel={s.manualAssets.add} />
{manualAssets.isLoading ? (
<SkeletonRows count={3} />
) : assets.length === 0 ? (
<EmptyBlock title={s.manualAssets.emptyTitle} subtitle={s.manualAssets.emptySubtitle} />
<Card style={{ padding: 0 }}>
<EmptyState icon="layers" title={s.manualAssets.emptyTitle} hint={s.manualAssets.emptySubtitle} />
</Card>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{assets.map((asset) => (
@ -113,11 +145,13 @@ export function AssetsView() {
</section>
<section style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<SectionHeaderRow title={s.lending.title} onAdd={() => setAddLoanOpen(true)} addLabel={s.lending.add} />
<SectionHeader title={s.lending.title} onAction={() => setAddLoanOpen(true)} actionLabel={s.lending.add} />
{lending.isLoading ? (
<SkeletonRows count={2} />
) : loans.length === 0 ? (
<EmptyText>{s.lending.empty}</EmptyText>
<Card style={{ padding: 0 }}>
<EmptyState icon="hand-coins" title={s.lending.empty} hint={s.lending.emptyHint} />
</Card>
) : (
<Card style={{ padding: 0 }}>
{loans.map((loan, i) => (
@ -201,21 +235,21 @@ export function AssetsView() {
function NetWorthCard({ netWorth, loading }: { netWorth?: { total: string; assets: string; liabilities: string }; loading: boolean }) {
return (
<Card style={{ padding: "24px 20px", background: "var(--mercury-balance-card)" }}>
<Card style={{ padding: "26px 22px", background: "var(--mercury-balance-card)" }}>
{loading ? (
<Skeleton height="40px" />
) : (
<>
<div style={{ fontSize: 13, opacity: 0.75 }}>{s.netWorth.total}</div>
<div style={{ fontSize: 32, fontWeight: 700, marginTop: 6 }}>{tugrik(netWorth?.total ?? "0")}</div>
<div style={{ display: "flex", gap: 20, marginTop: 16 }}>
<div style={{ fontSize: 13, fontWeight: 600, letterSpacing: 0.2, opacity: 0.75 }}>{s.netWorth.total}</div>
<div style={{ fontSize: 34, fontWeight: 800, marginTop: 8, letterSpacing: -0.5 }}>{tugrik(netWorth?.total ?? "0")}</div>
<div style={{ display: "flex", gap: 28, marginTop: 20 }}>
<div>
<div style={{ fontSize: 12, opacity: 0.75 }}>{s.netWorth.assets}</div>
<div style={{ fontWeight: 600 }}>{tugrik(netWorth?.assets ?? "0")}</div>
<div style={{ fontSize: 15, fontWeight: 700, marginTop: 2 }}>{tugrik(netWorth?.assets ?? "0")}</div>
</div>
<div>
<div style={{ fontSize: 12, opacity: 0.75 }}>{s.netWorth.liabilities}</div>
<div style={{ fontWeight: 600 }}>{tugrik(netWorth?.liabilities ?? "0")}</div>
<div style={{ fontSize: 15, fontWeight: 700, marginTop: 2 }}>{tugrik(netWorth?.liabilities ?? "0")}</div>
</div>
</div>
</>
@ -226,48 +260,13 @@ function NetWorthCard({ netWorth, loading }: { netWorth?: { total: string; asset
// --- Small shared bits -------------------------------------------------------
function SectionTitle({ children }: { children: React.ReactNode }) {
return <h2 style={{ margin: 0, fontSize: 14, fontWeight: 700 }}>{children}</h2>;
}
function SectionHeaderRow({ title, onAdd, addLabel }: { title: string; onAdd: () => void; addLabel: string }) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<SectionTitle>{title}</SectionTitle>
<button
type="button"
onClick={onAdd}
aria-label={addLabel}
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 22, lineHeight: 1, padding: 4 }}
>
+
</button>
</div>
);
}
function EmptyText({ children }: { children: React.ReactNode }) {
return (
<p style={{ ...mutedStyle, fontSize: 13, padding: "12px 4px", margin: 0 }}>{children}</p>
);
}
function EmptyBlock({ title, subtitle }: { title: string; subtitle: string }) {
return (
<div style={{ textAlign: "center", padding: "40px 20px", display: "flex", flexDirection: "column", gap: 8 }}>
<div style={{ fontWeight: 600 }}>{title}</div>
<div style={{ fontSize: 13, ...mutedStyle }}>{subtitle}</div>
</div>
);
}
function SkeletonRows({ count }: { count: number }) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<Card style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{Array.from({ length: count }).map((_, i) => (
<Skeleton key={i} height="64px" />
<Skeleton key={i} height="48px" style={{ borderRadius: "var(--seed-radius-r2, 8px)" }} />
))}
</div>
</Card>
);
}
@ -292,25 +291,30 @@ function ManualAssetRow({
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 (
<Card style={{ padding: 0 }}>
<div style={rowStyle}>
<Link href={`/assets/manual/${encodeURIComponent(asset.name)}`} style={{ color: "inherit", textDecoration: "none", flex: 1 }}>
<div style={{ fontWeight: 700 }}>{asset.name}</div>
<div style={{ fontSize: 12, ...mutedStyle }}>
{categoryLabel} · {conditionLabel}
<Link
href={`/assets/manual/${encodeURIComponent(asset.name)}`}
style={{ display: "flex", alignItems: "center", gap: 12, color: "inherit", textDecoration: "none", flex: 1, minWidth: 0 }}
>
<IconChip icon={chip.icon} tint={chip.tint} fg={chip.fg} />
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span style={titleStyle}>{asset.name}</span>
<span style={subtitleStyle}>
{categoryLabel} · {conditionLabel}
</span>
</div>
</Link>
<div style={{ textAlign: "right" }}>
<div style={{ fontWeight: 700 }}>{tugrikRaw(asset.value)}</div>
<div style={{ fontSize: 11, ...mutedStyle }}>
{s.manualAssets.acquiredPrefix}: {tugrikRaw(asset.acquiredValue)}
</div>
<div style={{ textAlign: "right", flexShrink: 0 }}>
<div style={amountStyle}>{tugrikRaw(asset.value)}</div>
<div
style={{
fontSize: 12,
fontWeight: 600,
marginTop: 2,
color: positive ? "var(--seed-color-fg-positive)" : "var(--seed-color-fg-critical)",
}}
>
@ -319,32 +323,40 @@ function ManualAssetRow({
</div>
</div>
</div>
<div style={{ display: "flex", gap: 8, padding: "0 16px 12px" }}>
<div style={{ display: "flex", gap: 8, padding: "0 16px 14px" }}>
<button
type="button"
onClick={onRevalue}
disabled={revaluing}
style={{
flex: 1,
border: "1px solid var(--seed-color-border-neutral, #e5e5e5)",
borderRadius: 8,
background: "none",
padding: "6px 10px",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 6,
border: "none",
borderRadius: 10,
background: "var(--seed-color-bg-neutral-subtle, #eef0f2)",
padding: "8px 10px",
fontSize: 12,
fontWeight: 600,
color: "var(--seed-color-fg-neutral)",
cursor: revaluing ? "default" : "pointer",
}}
>
<Icon name="trending" size={14} />
{revaluing ? "…" : s.manualAssets.revalue}
</button>
<button
type="button"
onClick={onDelete}
style={{
border: "1px solid var(--seed-color-border-neutral, #e5e5e5)",
borderRadius: 8,
background: "none",
padding: "6px 10px",
border: "none",
borderRadius: 10,
background: "var(--seed-color-bg-neutral-subtle, #eef0f2)",
padding: "8px 14px",
fontSize: 12,
fontWeight: 600,
color: "var(--seed-color-fg-critical)",
cursor: "pointer",
}}
@ -373,21 +385,36 @@ function statusLabel(loan: Lending): string {
function LoanRow({ loan, onDelete }: { loan: Lending; onDelete: () => void }) {
return (
<div style={rowStyle}>
<Link href={`/assets/lending/${loan.id}`} style={{ color: "inherit", textDecoration: "none", flex: 1 }}>
<div style={{ fontWeight: 600 }}>{loan.person}</div>
<div style={{ fontSize: 12, color: loan.overdue ? "var(--seed-color-fg-critical)" : "var(--seed-color-fg-neutral-subtle)" }}>
{statusLabel(loan)}
<Link
href={`/assets/lending/${loan.id}`}
style={{ display: "flex", alignItems: "center", gap: 12, color: "inherit", textDecoration: "none", flex: 1, minWidth: 0 }}
>
<IconChip icon="hand-coins" {...LENDING_TINT} />
<div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
<span style={titleStyle}>{loan.person}</span>
<span style={{ fontSize: 12, color: loan.overdue ? "var(--seed-color-fg-critical)" : "var(--seed-color-fg-neutral-subtle)" }}>
{statusLabel(loan)}
</span>
</div>
</Link>
<div style={{ textAlign: "right" }}>
<div style={{ fontWeight: 700 }}>{tugrikRaw(loan.remaining)}</div>
<div style={{ fontSize: 12, ...mutedStyle }}>/ {tugrikRaw(loan.principal)}</div>
<div style={{ textAlign: "right", flexShrink: 0 }}>
<div style={amountStyle}>{tugrikRaw(loan.remaining)}</div>
<div style={subtitleStyle}>/ {tugrikRaw(loan.principal)}</div>
</div>
<button
type="button"
onClick={onDelete}
aria-label={s.lending.delete}
style={{ background: "none", border: "none", color: "var(--seed-color-fg-neutral-subtle)", cursor: "pointer", fontSize: 16 }}
style={{
background: "none",
border: "none",
color: "var(--seed-color-fg-neutral-subtle)",
cursor: "pointer",
fontSize: 18,
lineHeight: 1,
padding: 4,
flexShrink: 0,
}}
>
×
</button>

View file

@ -0,0 +1,47 @@
"use client";
import Link from "next/link";
import { Icon } from "@/ds";
import { assetsStrings as s } from "./strings";
/**
* Shared header for the account / manual-asset / lending detail pages: a
* round back button + title, matching the row/typography language used
* across the rest of the Assets feature.
*/
export function DetailHeader({ title }: { title: string }) {
return (
<header style={{ display: "flex", alignItems: "center", gap: 12 }}>
<Link
href="/assets"
aria-label={s.common.back}
style={{
display: "grid",
placeItems: "center",
width: 36,
height: 36,
flexShrink: 0,
borderRadius: 12,
background: "var(--seed-color-bg-neutral-subtle, #eef0f2)",
color: "var(--seed-color-fg-neutral)",
}}
>
<Icon name="chevron-left" size={20} />
</Link>
<h1
style={{
margin: 0,
fontSize: 18,
fontWeight: 700,
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{title}
</h1>
</header>
);
}

View file

@ -18,10 +18,11 @@ import {
import { useLending } from "@/api/hooks/reads";
import { useLendingMutations } from "@/api/hooks/mutations";
import type { Lending } from "@/api/schemas";
import { Card, MercuryButton } from "@/ds";
import { Card, MercuryButton, IconChip, EmptyState } from "@/ds";
import { tugrikRaw } from "@/ds/money";
import { assetsStrings as s } from "./strings";
import { ConfirmDialog } from "./ConfirmDialog";
import { DetailHeader } from "./DetailHeader";
type LendingRepayment = Lending["repayments"][number];
@ -70,7 +71,7 @@ export function LendingDetail({ id }: LendingDetailProps) {
if (!loan) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<BackLink />
<DetailHeader title={s.lending.title} />
<p style={mutedStyle}>{s.lending.empty}</p>
</div>
);
@ -84,12 +85,12 @@ export function LendingDetail({ id }: LendingDetailProps) {
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 }}>{loan.person}</h1>
</header>
<DetailHeader title={loan.person} />
<Card style={{ textAlign: "center", padding: "24px 20px" }}>
<div style={{ display: "flex", justifyContent: "center", marginBottom: 12 }}>
<IconChip icon="hand-coins" tint="#E3F0D8" fg="#3F7A1E" size={48} />
</div>
<div style={{ fontSize: 32, fontWeight: 700 }}>{tugrikRaw(loan.remaining)}</div>
<div style={{ marginTop: 6, ...mutedStyle }}>
{s.lending.total} <strong>{tugrikRaw(loan.principal)}</strong>
@ -116,25 +117,28 @@ export function LendingDetail({ id }: LendingDetailProps) {
<strong style={{ color: "var(--seed-color-fg-positive)" }}>{tugrikRaw(loan.repaid)}</strong>
</div>
{loan.repayments.length === 0 ? (
<p style={{ fontSize: 12, ...mutedStyle, margin: 0 }}>{s.lending.repayments.empty}</p>
<EmptyState icon="receipt" title={s.lending.repayments.empty} compact />
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
{loan.repayments.map((r, i) => (
<React.Fragment key={r.id}>
{i > 0 && <div style={{ height: 1, background: "var(--seed-color-border-neutral, #e5e5e5)" }} />}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 0" }}>
<div>
<div style={{ fontWeight: 600 }}>{tugrikRaw(r.amount)}</div>
<div style={{ fontSize: 12, ...mutedStyle }}>
{r.paidOn}
{r.note ? ` · ${r.note}` : ""}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, padding: "10px 0" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
<IconChip icon="receipt" tint="#E1E5EA" fg="#42505F" size={36} />
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 15, fontWeight: 700 }}>{tugrikRaw(r.amount)}</div>
<div style={{ fontSize: 12, ...mutedStyle }}>
{r.paidOn}
{r.note ? ` · ${r.note}` : ""}
</div>
</div>
</div>
<button
type="button"
onClick={() => setDeletingRepayment(r)}
aria-label={s.lending.repayments.deleteTitle}
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 16, color: "var(--seed-color-fg-neutral-subtle)" }}
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 18, flexShrink: 0, color: "var(--seed-color-fg-neutral-subtle)" }}
>
×
</button>
@ -190,14 +194,6 @@ export function LendingDetail({ id }: LendingDetailProps) {
);
}
function BackLink() {
return (
<a href="/assets" style={{ textDecoration: "none", color: "inherit", fontWeight: 600 }}>
{s.common.back}
</a>
);
}
function AddRepaymentSheet({
onClose,
onSave,

View file

@ -9,11 +9,23 @@ import { apiGet } from "@/api/client";
import { useManualAssets } from "@/api/hooks/reads";
import { useManualAssetMutations } from "@/api/hooks/mutations";
import { AssetPointSchema, AssetListingSchema, type ManualAsset, type RevalueResult } from "@/api/schemas";
import { Card, MercuryButton } from "@/ds";
import { Card, MercuryButton, IconChip, type IconName } from "@/ds";
import { tugrikRaw, tugrikShortRaw } from "@/ds/money";
import { assetsStrings as s } from "./strings";
import { ValueHistoryChart } from "./ValueHistoryChart";
import { ConfirmDialog } from "./ConfirmDialog";
import { DetailHeader } from "./DetailHeader";
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" };
}
}
// --- Local reads (no hook exists yet for asset history/listings; these mirror
// the shape of reads.ts's other hooks and hit the same endpoints iOS uses:
@ -83,7 +95,7 @@ export function ManualAssetDetail({ name }: ManualAssetDetailProps) {
if (!asset) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<BackLink />
<DetailHeader title={name} />
<p style={mutedStyle}>{name}</p>
</div>
);
@ -109,14 +121,16 @@ export function ManualAssetDetail({ name }: ManualAssetDetailProps) {
const categoryLabel = s.manualAssets.categories[asset.category] ?? asset.category;
const conditionLabel = s.manualAssets.conditions[asset.condition] ?? asset.condition;
const chip = assetChip(asset.category);
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 }}>{asset.name}</h1>
</header>
<DetailHeader title={asset.name} />
<Card style={{ textAlign: "center", padding: "24px 20px" }}>
<div style={{ display: "flex", justifyContent: "center", marginBottom: 12 }}>
<IconChip icon={chip.icon} tint={chip.tint} fg={chip.fg} size={48} />
</div>
<div style={{ fontSize: 12, ...mutedStyle }}>{categoryLabel} · {conditionLabel}</div>
<div style={{ fontSize: 32, fontWeight: 700, marginTop: 8 }}>{tugrikRaw(value)}</div>
<div
@ -240,14 +254,6 @@ export function ManualAssetDetail({ name }: ManualAssetDetailProps) {
);
}
function BackLink() {
return (
<a href="/assets" style={{ textDecoration: "none", color: "inherit", fontWeight: 600 }}>
{s.common.back}
</a>
);
}
function DetailRow({ label, value, color }: { label: string; value: string; color?: string }) {
return (
<div style={{ display: "flex", justifyContent: "space-between", padding: "14px 0" }}>

View file

@ -14,6 +14,7 @@ export const assetsStrings = {
accounts: {
title: "Миний дансууд",
empty: "Холбосон данс алга",
emptyHint: "Банкны дансаа холбомогц энд харагдана",
},
manualAssets: {
title: "Хөрөнгийн жагсаалт",
@ -65,6 +66,7 @@ export const assetsStrings = {
lending: {
title: "Найзуудад өгсөн зээл",
empty: "Зээл бүртгэгдээгүй байна",
emptyHint: "Найздаа зээл өгсөн бол + дарж бүртгээрэй",
add: "Шинэ зээл",
total: "нийт",
statusPaid: "Төлсөн",