import { useState, useEffect } from "react"; import { X, Search, Loader2 } from "lucide-react"; import { searchAssets, createHolding, addInvestmentTransaction, AssetSearchResult } from "@/api/investments"; import { createTransaction } from "@/api/transactions"; import { useUiStore } from "@/store/uiStore"; import { format } from "date-fns"; const COMMON_CURRENCIES = ["GBP", "USD", "EUR", "JPY", "CAD", "AUD", "CHF"]; interface Account { id: string; name: string; type: string; } interface Props { accounts: Account[]; onClose: () => void; onSuccess: () => void; } export default function AddHoldingModal({ accounts, onClose, onSuccess }: Props) { const baseCurrency = useUiStore(s => s.currency); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [searching, setSearching] = useState(false); const [selected, setSelected] = useState(null); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const investAccounts = accounts.filter(a => ["investment", "pension", "savings", "other"].includes(a.type) ); const [priceMode, setPriceMode] = useState<"per_share" | "total">("per_share"); const [recordCash, setRecordCash] = useState(false); const [form, setForm] = useState({ account_id: investAccounts[0]?.id ?? "", quantity: "", price: "", currency: baseCurrency, fees: "0", date: format(new Date(), "yyyy-MM-dd"), }); useEffect(() => { const t = setTimeout(async () => { if (query.length < 1) { setResults([]); return; } setSearching(true); try { const r = await searchAssets(query); setResults(r); } finally { setSearching(false); } }, 400); return () => clearTimeout(t); }, [query]); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!selected || !form.account_id || !form.quantity || !form.price) return; setSaving(true); setError(null); try { const qty = parseFloat(form.quantity); const rawPrice = parseFloat(form.price); const price = priceMode === "total" ? rawPrice / qty : rawPrice; const holding = await createHolding({ account_id: form.account_id, asset_id: selected.id, quantity: 0, avg_cost_basis: 0, currency: form.currency, }); await addInvestmentTransaction({ holding_id: holding.id, type: "buy", quantity: qty, price: price, fees: parseFloat(form.fees) || 0, currency: form.currency, date: form.date, }); if (recordCash) { const totalSpent = qty * price + (parseFloat(form.fees) || 0); await createTransaction({ account_id: form.account_id, type: "investment", amount: -totalSpent, currency: form.currency, date: form.date, description: `Buy ${selected.symbol} × ${qty}`, status: "cleared", }); } onSuccess(); } catch (e: any) { const detail = e?.response?.data?.detail; setError(Array.isArray(detail) ? detail.map((d: any) => d.msg).join(", ") : (detail ?? "Failed to add holding")); } finally { setSaving(false); } } return (

Add Holding

{/* Asset search */}
{ setQuery(e.target.value); setSelected(null); }} placeholder="e.g. AAPL, Vanguard, BTC..." className="w-full pl-9 pr-3 py-2 rounded-md border border-input bg-background text-sm focus:outline-none focus:ring-2 focus:ring-ring" /> {searching && }
{results.length > 0 && !selected && (
{results.map((r) => ( ))}
)} {selected && (

✓ Selected: {selected.symbol} ({selected.name})

)}
{/* Account */}
{/* Quantity / Price / Fees */}
setForm(f => ({ ...f, quantity: e.target.value }))} className="w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring" placeholder="10" />
setForm(f => ({ ...f, price: e.target.value }))} className="w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring" placeholder={priceMode === "per_share" ? "150.00" : "1500.00"} />
{form.price && form.quantity && parseFloat(form.quantity) > 0 && (

{priceMode === "per_share" ? `Total: ${form.currency} ${(parseFloat(form.price) * parseFloat(form.quantity)).toFixed(2)}` : `Per share: ${form.currency} ${(parseFloat(form.price) / parseFloat(form.quantity)).toFixed(4)}` }

)}
setForm(f => ({ ...f, fees: e.target.value }))} className="w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring" placeholder="0" />
{selected && form.currency !== selected.currency && (

Price in {form.currency} — asset trades in {selected.currency}. Current values will be converted using live exchange rates.

)}
setForm(f => ({ ...f, date: e.target.value }))} className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" />
{/* Cash purchase toggle */}
); }