From 43108a92bb98d427973e6a072cd2aa318274e05a Mon Sep 17 00:00:00 2001 From: Munkherdene Date: Sat, 22 Aug 2026 22:16:05 +0800 Subject: [PATCH] feat(web): rework assets visuals to reference bar --- src/features/assets/AccountDetail.tsx | 76 +++++---- src/features/assets/AssetsView.tsx | 189 ++++++++++++---------- src/features/assets/DetailHeader.tsx | 47 ++++++ src/features/assets/LendingDetail.tsx | 40 +++-- src/features/assets/ManualAssetDetail.tsx | 34 ++-- src/features/assets/strings.ts | 2 + 6 files changed, 236 insertions(+), 152 deletions(-) create mode 100644 src/features/assets/DetailHeader.tsx diff --git a/src/features/assets/AccountDetail.tsx b/src/features/assets/AccountDetail.tsx index 1a64c78..27693e4 100644 --- a/src/features/assets/AccountDetail.tsx +++ b/src/features/assets/AccountDetail.tsx @@ -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 (
-
- -

{account?.bank ?? s.accountDetail.bank}

-
+
{account?.accountNumber}
@@ -44,37 +44,22 @@ export function AccountDetail({ accountId }: AccountDetailProps) {
-
-

{s.accountDetail.recentTransactions}

+
+

{s.accountDetail.recentTransactions}

{transactions.isLoading ? (

) : (transactions.data ?? []).length === 0 ? ( -

{s.accountDetail.noTransactions}

+ + + ) : ( - {(transactions.data ?? []).map((txn, i) => { - const income = txn.direction === "income"; - return ( - - {i > 0 && } -
-
-
{txn.title || txn.category}
-
{txn.date.slice(0, 10)}
-
-
- {income ? "+" : "−"} - {tugrikRaw(txn.amount)} -
-
-
- ); - })} + {(transactions.data ?? []).map((txn, i) => ( + + {i > 0 && } + + + ))}
)}
@@ -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 ( - - ← {s.common.back} - +
+
+ +
+ + {txn.title || cat.name} + + {txn.date.slice(0, 10)} +
+
+ + {income ? "+" : "−"} + {tugrikRaw(txn.amount)} + +
); } diff --git a/src/features/assets/AssetsView.tsx b/src/features/assets/AssetsView.tsx index 6f46e35..743437a 100644 --- a/src/features/assets/AssetsView.tsx +++ b/src/features/assets/AssetsView.tsx @@ -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() {
- {s.accounts.title} + {netWorth.isLoading ? ( ) : accounts.length === 0 ? ( - {s.accounts.empty} + + + ) : ( {accounts.map((account, i) => ( @@ -79,11 +106,14 @@ export function AssetsView() { href={`/assets/account/${account.accountId}`} style={{ ...rowStyle, color: "inherit", textDecoration: "none" }} > -
-
{account.bank}
-
{account.accountNumber}
+
+ +
+ {account.bank} + {account.accountNumber} +
-
{tugrik(account.balance)}
+ {tugrik(account.balance)} ))} @@ -92,11 +122,13 @@ export function AssetsView() {
- setAddAssetOpen(true)} addLabel={s.manualAssets.add} /> + setAddAssetOpen(true)} actionLabel={s.manualAssets.add} /> {manualAssets.isLoading ? ( ) : assets.length === 0 ? ( - + + + ) : (
{assets.map((asset) => ( @@ -113,11 +145,13 @@ export function AssetsView() {
- setAddLoanOpen(true)} addLabel={s.lending.add} /> + setAddLoanOpen(true)} actionLabel={s.lending.add} /> {lending.isLoading ? ( ) : loans.length === 0 ? ( - {s.lending.empty} + + + ) : ( {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 ( - + {loading ? ( ) : ( <> -
{s.netWorth.total}
-
{tugrik(netWorth?.total ?? "0")}
-
+
{s.netWorth.total}
+
{tugrik(netWorth?.total ?? "0")}
+
{s.netWorth.assets}
-
{tugrik(netWorth?.assets ?? "0")}
+
{tugrik(netWorth?.assets ?? "0")}
{s.netWorth.liabilities}
-
{tugrik(netWorth?.liabilities ?? "0")}
+
{tugrik(netWorth?.liabilities ?? "0")}
@@ -226,48 +260,13 @@ function NetWorthCard({ netWorth, loading }: { netWorth?: { total: string; asset // --- Small shared bits ------------------------------------------------------- -function SectionTitle({ children }: { children: React.ReactNode }) { - return

{children}

; -} - -function SectionHeaderRow({ title, onAdd, addLabel }: { title: string; onAdd: () => void; addLabel: string }) { - return ( -
- {title} - -
- ); -} - -function EmptyText({ children }: { children: React.ReactNode }) { - return ( -

{children}

- ); -} - -function EmptyBlock({ title, subtitle }: { title: string; subtitle: string }) { - return ( -
-
{title}
-
{subtitle}
-
- ); -} - function SkeletonRows({ count }: { count: number }) { return ( -
+ {Array.from({ length: count }).map((_, i) => ( - + ))} -
+ ); } @@ -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 (
- -
{asset.name}
-
- {categoryLabel} · {conditionLabel} + + +
+ {asset.name} + + {categoryLabel} · {conditionLabel} +
-
-
{tugrikRaw(asset.value)}
-
- {s.manualAssets.acquiredPrefix}: {tugrikRaw(asset.acquiredValue)} -
+
+
{tugrikRaw(asset.value)}
@@ -319,32 +323,40 @@ function ManualAssetRow({
-
+
diff --git a/src/features/assets/DetailHeader.tsx b/src/features/assets/DetailHeader.tsx new file mode 100644 index 0000000..fee070e --- /dev/null +++ b/src/features/assets/DetailHeader.tsx @@ -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 ( +
+ + + +

+ {title} +

+
+ ); +} diff --git a/src/features/assets/LendingDetail.tsx b/src/features/assets/LendingDetail.tsx index 69debe2..4f2865f 100644 --- a/src/features/assets/LendingDetail.tsx +++ b/src/features/assets/LendingDetail.tsx @@ -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 (
- +

{s.lending.empty}

); @@ -84,12 +85,12 @@ export function LendingDetail({ id }: LendingDetailProps) { return (
-
- -

{loan.person}

-
+ +
+ +
{tugrikRaw(loan.remaining)}
{s.lending.total} {tugrikRaw(loan.principal)} @@ -116,25 +117,28 @@ export function LendingDetail({ id }: LendingDetailProps) { {tugrikRaw(loan.repaid)}
{loan.repayments.length === 0 ? ( -

{s.lending.repayments.empty}

+ ) : (
{loan.repayments.map((r, i) => ( {i > 0 &&
} -
-
-
{tugrikRaw(r.amount)}
-
- {r.paidOn} - {r.note ? ` · ${r.note}` : ""} +
+
+ +
+
{tugrikRaw(r.amount)}
+
+ {r.paidOn} + {r.note ? ` · ${r.note}` : ""} +
@@ -190,14 +194,6 @@ export function LendingDetail({ id }: LendingDetailProps) { ); } -function BackLink() { - return ( - - ← {s.common.back} - - ); -} - function AddRepaymentSheet({ onClose, onSave, diff --git a/src/features/assets/ManualAssetDetail.tsx b/src/features/assets/ManualAssetDetail.tsx index 99ec9a8..058f62f 100644 --- a/src/features/assets/ManualAssetDetail.tsx +++ b/src/features/assets/ManualAssetDetail.tsx @@ -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 (
- +

{name}

); @@ -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 (
-
- -

{asset.name}

-
+ +
+ +
{categoryLabel} · {conditionLabel}
{tugrikRaw(value)}
- ← {s.common.back} - - ); -} - function DetailRow({ label, value, color }: { label: string; value: string; color?: string }) { return (
diff --git a/src/features/assets/strings.ts b/src/features/assets/strings.ts index a406ce7..8483019 100644 --- a/src/features/assets/strings.ts +++ b/src/features/assets/strings.ts @@ -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: "Төлсөн",