import { useEffect, useMemo, useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { TimeSeriesChart } from "@/components/charts/TimeSeriesChart"; import { SegmentedControl } from "@/components/charts/SegmentedControl"; import { Loader2, TrendingUp, TrendingDown, Info } from "lucide-react"; import { api } from "@/lib/api"; import { cn } from "@/lib/utils"; import type { Holding } from "@/types/finance"; import { usePrivacy } from "@/lib/privacy"; import { formatMoney } from "@/lib/format"; const PERIODS = ["1mo", "3mo", "6mo", "1y", "2y", "5y", "10y"] as const; const PERIOD_LABEL: Record = { "1mo": "1M", "3mo": "3M", "6mo": "6M", "1y": "1Y", "2y": "2Y", "5y": "5Y", "10y": "10Y" }; const PERIOD_OPTIONS = PERIODS.map((p) => ({ value: p as string, label: PERIOD_LABEL[p] })); type Quote = Awaited>; function fmtPrice(n: number, currency: string) { return formatMoney(n, currency, { decimals: 2 }); } interface Props { holding: Holding | null; open: boolean; onOpenChange: (open: boolean) => void; } /** * Live price + history for a manual holding linked to a public ticker (the * SpaceX-went-public case). Resolves the holding's symbol via the backend * /market/history endpoint (yfinance). Shows guidance when the symbol has no * public market data (still private / wrong ticker). */ export function HoldingChart({ holding, open, onOpenChange }: Props) { // Local formatter reads privacy mode; subscribe so toggling it re-renders. usePrivacy(); const [period, setPeriod] = useState("1y"); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const symbol = holding?.symbol ?? ""; const currency = holding?.currency ?? "USD"; useEffect(() => { if (!open || !symbol) return; let cancelled = false; setLoading(true); setData(null); api.market .history(symbol, currency, period) .then((res) => { if (!cancelled) setData(res); }) .catch(() => { if (!cancelled) setData(null); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [open, symbol, currency, period]); const up = (data?.changePercent ?? 0) >= 0; const series = useMemo( () => (data?.series ?? []).map((p) => ({ date: String(p.date).slice(0, 10), value: p.close })), [data] ); return ( {holding?.name} {data?.found && data.resolved && ( ({data.resolved}) )} {loading ? (

Fetching live market data…

) : data?.found && data.series.length > 1 ? (

{fmtPrice(data.price ?? 0, data.currency)}

{up ? : } {up ? "+" : ""}{fmtPrice(data.change ?? 0, data.currency)} ({up ? "+" : ""}{data.changePercent?.toFixed(2)}%)

fmtPrice(v, data.currency)} formatAxisValue={(v) => fmtPrice(v, data.currency)} yAxisWidth={60} />

Live market data via Yahoo Finance. Tracking {symbol}.

) : (

No public market data for “{symbol}”

This looks like a private or unlisted holding. Once it goes public, edit the holding and set its public ticker (e.g. AAPL, RELIANCE) to track the live price here.

)}
); }