"use client"; import * as React from "react"; import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; import { Skeleton } from "@seed-design/react"; 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, 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: // GET /manual-assets/{name}/history and /listings — see MercuryAPI.swift). --- const HistoryResponseSchema = z.object({ history: z.array(AssetPointSchema) }); const ListingsResponseSchema = z.object({ listings: z.array(AssetListingSchema) }); function useAssetHistory(name: string) { return useQuery({ queryKey: ["assetHistory", name], queryFn: async () => (await apiGet(`/manual-assets/${encodeURIComponent(name)}/history`, HistoryResponseSchema)).history, }); } function useAssetListings(name: string) { return useQuery({ queryKey: ["assetListings", name], queryFn: async () => (await apiGet(`/manual-assets/${encodeURIComponent(name)}/listings`, ListingsResponseSchema)).listings, }); } const mutedStyle: React.CSSProperties = { color: "var(--seed-color-fg-neutral-subtle)" }; export interface ManualAssetDetailProps { name: string; } export function ManualAssetDetail({ name }: ManualAssetDetailProps) { const router = useRouter(); const manualAssets = useManualAssets(); const history = useAssetHistory(name); const listings = useAssetListings(name); const mutations = useManualAssetMutations(); const [confirmRevalue, setConfirmRevalue] = React.useState(false); const [confirmDelete, setConfirmDelete] = React.useState(false); const [lastFinding, setLastFinding] = React.useState(null); const [revalueFailed, setRevalueFailed] = React.useState(false); const asset: ManualAsset | undefined = (manualAssets.data ?? []).find((a) => a.name === name); async function runRevalue() { setConfirmRevalue(false); setRevalueFailed(false); const result = await mutations.revalue.mutateAsync(name); setLastFinding(result ?? null); setRevalueFailed(!result); await Promise.all([history.refetch(), listings.refetch()]); } async function runDelete() { setConfirmDelete(false); await mutations.delete.mutateAsync(name); router.push("/assets"); } if (manualAssets.isLoading && !asset) { return (
); } if (!asset) { return (

{name}

); } const value = Number(asset.value || "0"); const acquired = Number(asset.acquiredValue || "0"); const change = Number(asset.change || "0"); const positive = change >= 0; const percent = acquired > 0 ? (change / acquired) * 100 : null; const points = (history.data ?? []) .map((p) => ({ date: new Date(p.recordedAt), value: Number(p.value) })) .filter((p) => !Number.isNaN(p.date.getTime()) && !Number.isNaN(p.value)) .sort((a, b) => a.date.getTime() - b.date.getTime()); const fetchPoints = (history.data ?? []) .filter((p) => p.kind === "fetch") .slice() .sort((a, b) => (a.recordedAt < b.recordedAt ? 1 : -1)); const listingRows = listings.data ?? []; const categoryLabel = s.manualAssets.categories[asset.category] ?? asset.category; const conditionLabel = s.manualAssets.conditions[asset.condition] ?? asset.condition; const chip = assetChip(asset.category); return (
{categoryLabel} · {conditionLabel}
{tugrikRaw(value)}
{positive ? "+" : "−"} {tugrikRaw(Math.abs(change))} {percent !== null && ` (${positive ? "+" : "−"}${Math.abs(percent).toFixed(1)}%)`}
{lastFinding && (
{lastFinding.source} — {lastFinding.count} {s.assetDetail.findingSuffix}
{tugrikShortRaw(lastFinding.low)}–{tugrikShortRaw(lastFinding.high)} · {s.assetDetail.avg}{" "} {tugrikShortRaw(lastFinding.value)}
)} {revalueFailed && !lastFinding && (
{s.assetDetail.notFound}
)}

{s.assetDetail.chartTitle}

{history.isLoading ? : }
{!history.isLoading && (

{s.assetDetail.research}

{fetchPoints.length > 0 && (
{fetchPoints.map((p, i) => (
{p.recordedAt.slice(0, 10)} {p.low && p.high ? `${tugrikShortRaw(p.low)}–${tugrikShortRaw(p.high)} · ` : ""} {p.count ?? 0} зар {tugrikShortRaw(p.value)}
))}
)}

{s.assetDetail.listings(listingRows.length)}

{listingRows.length === 0 ? (

{s.assetDetail.listingsEmpty}

) : (
{listingRows.slice(0, 15).map((l, i) => ( {l.title} {tugrikShortRaw(l.price)} ))}
)}
)}
setConfirmRevalue(true)} disabled={mutations.revalue.isPending}> {s.manualAssets.revalue} setConfirmDelete(true)} style={{ color: "var(--seed-color-fg-critical)" }}> {s.manualAssets.delete}
setConfirmRevalue(false)} onConfirm={runRevalue} /> setConfirmDelete(false)} onConfirm={runDelete} />
); } function DetailRow({ label, value, color }: { label: string; value: string; color?: string }) { return (
{label} {value}
); } function Divider() { return
; }