@burdenoff/microfe-brainyrich 2026.828.1 → 2026.828.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brainyrich/BrainyRichRoot.d.ts.map +1 -1
- package/dist/brainyrich/BrainyRichRoot.js +59 -59
- package/dist/brainyrich/BrainyRichRoot.js.map +1 -1
- package/dist/brainyrich/components/viz.d.ts.map +1 -1
- package/dist/brainyrich/components/viz.js +1 -1
- package/dist/brainyrich/components/viz.js.map +1 -1
- package/dist/brainyrich/pages/AiAssistantPage.js +192 -192
- package/dist/brainyrich/pages/AiAssistantPage.js.map +1 -1
- package/dist/brainyrich/pages/AnalyticsPage.js +9 -9
- package/dist/brainyrich/pages/AnalyticsPage.js.map +1 -1
- package/dist/brainyrich/pages/DashboardPage.js +115 -115
- package/dist/brainyrich/pages/DashboardPage.js.map +1 -1
- package/dist/brainyrich/pages/FamilyPage.js +59 -59
- package/dist/brainyrich/pages/FamilyPage.js.map +1 -1
- package/dist/brainyrich/pages/GoalsPage.js +285 -285
- package/dist/brainyrich/pages/GoalsPage.js.map +1 -1
- package/dist/brainyrich/pages/InvestmentsPage.js +18 -18
- package/dist/brainyrich/pages/InvestmentsPage.js.map +1 -1
- package/dist/brainyrich/pages/PortfoliosPage.d.ts.map +1 -1
- package/dist/brainyrich/pages/PortfoliosPage.js +322 -322
- package/dist/brainyrich/pages/PortfoliosPage.js.map +1 -1
- package/dist/brainyrich/pages/ReportsPage.js +17 -17
- package/dist/brainyrich/pages/ReportsPage.js.map +1 -1
- package/dist/brainyrich/pages/RiskPage.js +16 -16
- package/dist/brainyrich/pages/RiskPage.js.map +1 -1
- package/dist/brainyrich/pages/TaxPage.d.ts.map +1 -1
- package/dist/brainyrich/pages/TaxPage.js +14 -14
- package/dist/brainyrich/pages/TaxPage.js.map +1 -1
- package/dist/brainyrich/types/bra.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortfoliosPage.js","names":[],"sources":["../../../src/brainyrich/pages/PortfoliosPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownRight,\n ArrowUpRight,\n Briefcase,\n MoreHorizontal,\n Pencil,\n Plus,\n Trash2,\n} from 'lucide-react';\n\nimport {\n useAllHoldings,\n useArchivePortfolio,\n useCreateHolding,\n useCreatePortfolio,\n useDeleteHolding,\n useHouseholds,\n useModelPortfolios,\n usePagedPortfolios,\n usePortfolio,\n useUpdateHolding,\n useUpdatePortfolio,\n} from '../hooks/useBrainyRichApi';\nimport {\n AccountPicker,\n ConfirmDialog,\n DonutChart,\n FadeIn,\n GlassCard,\n LoadMore,\n StatTile,\n type DonutDatum,\n type VizTone,\n} from '../components';\nimport { CommentThread } from '../components/reuse/CommentThread';\nimport { TagField } from '../components/reuse/TagField';\nimport { hapticSuccess, showToast } from '../utils/nativeControls';\nimport { useTrackBrainyRichPageView } from '../hooks/useBrainyRichAnalytics';\nimport type { BraAssetClass, BraHolding, BraPortfolio, BraPortfolioStatus } from '../types/bra';\nimport {\n AllocationBars,\n EmptyState,\n ErrorState,\n LoadingState,\n SectionShell,\n deltaToneClass,\n formatCompact,\n formatMoney,\n formatPct,\n statusBadgeVariant,\n type AllocationDatum,\n} from './_shared';\n\nconst ASSET_CLASSES: BraAssetClass[] = [\n 'EQUITY',\n 'FIXED_INCOME',\n 'CASH',\n 'REAL_ESTATE',\n 'COMMODITY',\n 'CRYPTO',\n 'ALTERNATIVE',\n];\n\nconst PORTFOLIO_STATUSES: BraPortfolioStatus[] = ['DRAFT', 'ACTIVE', 'ARCHIVED'];\n\nconst NONE = '__none__';\n\nconst prettyEnum = (value: string): string =>\n value\n .toLowerCase()\n .split('_')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n\nconst toNumber = (value: string): number | undefined => {\n if (value.trim() === '') return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n};\n\n/** Stable, semantic tone per asset class so donut/legend colours read consistently. */\nconst ASSET_TONE: Record<string, VizTone> = {\n EQUITY: 'brand',\n FIXED_INCOME: 'blue',\n CASH: 'teal',\n REAL_ESTATE: 'purple',\n COMMODITY: 'warning',\n CRYPTO: 'indigo',\n ALTERNATIVE: 'info',\n};\n\nconst toneForClass = (raw: string): VizTone => ASSET_TONE[raw.toUpperCase()] ?? 'neutral';\n\ninterface TargetAllocationEntry {\n assetClass: string;\n targetPct: number;\n}\n\n/**\n * Read a portfolio's `targetAllocation` JSON scalar as `{ assetClass, targetPct }`\n * entries.\n *\n * The service persists and reads this as an ARRAY of `{ assetClass, targetPct }`\n * objects — `braCreatePortfolio` defaults it to `[]`, and the drift engine's\n * loader (`intelligence/lib/risk-data.ts#parseTargetAllocation`) discards\n * anything that isn't an array of those objects. Rows written before that shape\n * settled, as a flat `{ EQUITY: 60 }` map, are still read here so they don't\n * silently render as \"no target allocation\".\n */\nfunction readTargetAllocation(raw: unknown): TargetAllocationEntry[] {\n if (Array.isArray(raw)) {\n return raw.flatMap((entry) => {\n if (!entry || typeof entry !== 'object') return [];\n const { assetClass, targetPct } = entry as Record<string, unknown>;\n if (typeof assetClass !== 'string') return [];\n const pct = Number(targetPct);\n return Number.isFinite(pct) ? [{ assetClass, targetPct: pct }] : [];\n });\n }\n if (raw && typeof raw === 'object') {\n return Object.entries(raw as Record<string, unknown>).flatMap(([assetClass, value]) => {\n const pct = Number(value);\n return Number.isFinite(pct) ? [{ assetClass, targetPct: pct }] : [];\n });\n }\n return [];\n}\n\n/** Read a `targetAllocation` JSON blob as target-weight bars. */\nfunction parseTargetAllocation(raw: unknown): AllocationDatum[] {\n return readTargetAllocation(raw).map((d) => ({\n label: prettyEnum(d.assetClass),\n value: d.targetPct,\n tone: 'accent' as const,\n }));\n}\n\n/** Read a `targetAllocation` JSON blob as tone-keyed donut slices (percent values). */\nfunction targetAllocationDonut(raw: unknown): DonutDatum[] {\n return readTargetAllocation(raw)\n .filter((d) => d.targetPct > 0)\n .sort((a, b) => b.targetPct - a.targetPct)\n .map((d) => ({\n label: prettyEnum(d.assetClass),\n value: d.targetPct,\n tone: toneForClass(d.assetClass),\n }));\n}\n\n// ============================================================================\n// Portfolio valuation — real holdings rolled up per portfolio\n// ============================================================================\n\nexport interface PortfolioValuation {\n /** Σ holding.marketValue (server-derived `quantity * lastPrice`). */\n marketValue: number;\n /** Σ holding.costBasis. */\n costBasis: number;\n unrealizedGain: number;\n /** null when there is no cost basis to divide by. */\n gainPct: number | null;\n holdingCount: number;\n /** Largest absolute asset-class drift in points; null without a target or holdings. */\n maxDriftPct: number | null;\n /** The asset class carrying `maxDriftPct`. */\n driftAssetClass: string | null;\n}\n\nconst EMPTY_VALUATION: PortfolioValuation = {\n marketValue: 0,\n costBasis: 0,\n unrealizedGain: 0,\n gainPct: null,\n holdingCount: 0,\n maxDriftPct: null,\n driftAssetClass: null,\n};\n\n/**\n * Roll a portfolio's own holdings into value, unrealized gain and allocation\n * drift.\n *\n * Drift mirrors the service's deterministic engine exactly\n * (`intelligence/lib/risk-engine.ts`): `driftPct = actualAssetClassPct -\n * targetAssetClassPct`, over the union of the classes actually held and the\n * classes targeted, comparing the `assetClass` strings verbatim — the engine\n * does no case folding, so neither do we, or this card would report a different\n * number than the DRIFT insight raised against the same portfolio.\n *\n * Values are summed unconverted, which is also what the engine does: it never\n * applies an FX rate to `quantity * lastPrice`. Cross-portfolio totals are\n * therefore only ever presented per base currency.\n */\nfunction valuePortfolio(holdings: BraHolding[], targetAllocation: unknown): PortfolioValuation {\n if (holdings.length === 0) return EMPTY_VALUATION;\n\n let marketValue = 0;\n let costBasis = 0;\n const valueByClass = new Map<string, number>();\n for (const h of holdings) {\n marketValue += h.marketValue;\n costBasis += h.costBasis;\n valueByClass.set(h.assetClass, (valueByClass.get(h.assetClass) ?? 0) + h.marketValue);\n }\n\n const unrealizedGain = marketValue - costBasis;\n const targets = readTargetAllocation(targetAllocation);\n\n let maxDriftPct: number | null = null;\n let driftAssetClass: string | null = null;\n if (targets.length > 0 && marketValue > 0) {\n const classes = new Set<string>([...valueByClass.keys(), ...targets.map((t) => t.assetClass)]);\n for (const assetClass of classes) {\n const actualPct = ((valueByClass.get(assetClass) ?? 0) / marketValue) * 100;\n const targetPct = targets.find((t) => t.assetClass === assetClass)?.targetPct ?? 0;\n const driftPct = actualPct - targetPct;\n if (maxDriftPct === null || Math.abs(driftPct) > Math.abs(maxDriftPct)) {\n maxDriftPct = driftPct;\n driftAssetClass = assetClass;\n }\n }\n }\n\n return {\n marketValue,\n costBasis,\n unrealizedGain,\n gainPct: costBasis > 0 ? (unrealizedGain / costBasis) * 100 : null,\n holdingCount: holdings.length,\n maxDriftPct,\n driftAssetClass,\n };\n}\n\n// ============================================================================\n// Gain cell — signed money with icon + semantic color (color never sole signal)\n// ============================================================================\n\nfunction GainValue({ value, currency }: { value: number; currency: string }) {\n const positive = value > 0;\n const negative = value < 0;\n const tone = positive\n ? 'text-status-success-text'\n : negative\n ? 'text-status-error-text'\n : 'text-text-secondary';\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : null;\n return (\n <span className={`inline-flex items-center justify-end gap-1 font-medium ${tone}`}>\n {Icon ? <Icon className=\"size-3.5\" aria-hidden=\"true\" /> : null}\n {positive ? '+' : ''}\n {formatMoney(value, currency)}\n </span>\n );\n}\n\n// ============================================================================\n// Portfolio create / edit dialog\n// ============================================================================\n\ninterface PortfolioFormState {\n name: string;\n baseCurrency: string;\n description: string;\n driftThresholdPct: string;\n status: BraPortfolioStatus;\n modelPortfolioId: string;\n householdId: string;\n}\n\nfunction emptyForm(): PortfolioFormState {\n return {\n name: '',\n baseCurrency: 'USD',\n description: '',\n driftThresholdPct: '5',\n status: 'DRAFT',\n modelPortfolioId: NONE,\n householdId: NONE,\n };\n}\n\nfunction PortfolioFormDialog({\n open,\n onOpenChange,\n editing,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n editing: BraPortfolio | null;\n}) {\n const { data: models } = useModelPortfolios();\n const { data: households } = useHouseholds();\n const createPortfolio = useCreatePortfolio();\n const updatePortfolio = useUpdatePortfolio();\n\n const [form, setForm] = useState<PortfolioFormState>(emptyForm);\n // Re-seed the form whenever the dialog target changes.\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const targetKey = editing?.id ?? 'new';\n if (open && seededFor !== targetKey) {\n setSeededFor(targetKey);\n setForm(\n editing\n ? {\n name: editing.name,\n baseCurrency: editing.baseCurrency,\n description: editing.description ?? '',\n driftThresholdPct: String(editing.driftThresholdPct),\n status: editing.status,\n modelPortfolioId: editing.modelPortfolioId ?? NONE,\n householdId: editing.householdId ?? NONE,\n }\n : emptyForm()\n );\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const pending = createPortfolio.isPending || updatePortfolio.isPending;\n const nameValid = form.name.trim().length > 0;\n\n const handleSubmit = () => {\n if (!nameValid) return;\n const drift = toNumber(form.driftThresholdPct);\n const modelPortfolioId = form.modelPortfolioId === NONE ? null : form.modelPortfolioId;\n const householdId = form.householdId === NONE ? null : form.householdId;\n\n if (editing) {\n updatePortfolio.mutate(\n {\n id: editing.id,\n input: {\n name: form.name.trim(),\n baseCurrency: form.baseCurrency.trim() || 'USD',\n description: form.description.trim() || null,\n driftThresholdPct: drift,\n status: form.status,\n modelPortfolioId,\n },\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio saved');\n onOpenChange(false);\n },\n }\n );\n } else {\n createPortfolio.mutate(\n {\n name: form.name.trim(),\n baseCurrency: form.baseCurrency.trim() || 'USD',\n description: form.description.trim() || null,\n driftThresholdPct: drift,\n modelPortfolioId,\n householdId,\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio created');\n onOpenChange(false);\n },\n }\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{editing ? 'Edit portfolio' : 'New portfolio'}</DialogTitle>\n <DialogDescription>\n {editing\n ? 'Update this portfolio’s strategy settings.'\n : 'Create an investment portfolio to organize and rebalance holdings.'}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-name\">Name</Label>\n <Input\n id=\"pf-name\"\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Growth Portfolio\"\n aria-invalid={!nameValid}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-currency\">Base currency</Label>\n <Input\n id=\"pf-currency\"\n value={form.baseCurrency}\n onChange={(e) =>\n setForm((f) => ({ ...f, baseCurrency: e.target.value.toUpperCase() }))\n }\n placeholder=\"USD\"\n maxLength={3}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-drift\">Drift threshold %</Label>\n <Input\n id=\"pf-drift\"\n type=\"number\"\n min={0}\n step=\"0.5\"\n value={form.driftThresholdPct}\n onChange={(e) => setForm((f) => ({ ...f, driftThresholdPct: e.target.value }))}\n />\n </div>\n </div>\n\n {editing ? (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-status\">Status</Label>\n <Select\n value={form.status}\n onValueChange={(v) => setForm((f) => ({ ...f, status: v as BraPortfolioStatus }))}\n >\n <SelectTrigger id=\"pf-status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PORTFOLIO_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {prettyEnum(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-model\">Model portfolio</Label>\n <Select\n value={form.modelPortfolioId}\n onValueChange={(v) => setForm((f) => ({ ...f, modelPortfolioId: v }))}\n >\n <SelectTrigger id=\"pf-model\">\n <SelectValue placeholder=\"None\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>None</SelectItem>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n {m.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {!editing ? (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-household\">Household</Label>\n <Select\n value={form.householdId}\n onValueChange={(v) => setForm((f) => ({ ...f, householdId: v }))}\n >\n <SelectTrigger id=\"pf-household\">\n <SelectValue placeholder=\"None\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>None</SelectItem>\n {households.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-desc\">Description</Label>\n <Textarea\n id=\"pf-desc\"\n value={form.description}\n onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}\n placeholder=\"Optional notes about this strategy\"\n rows={3}\n />\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!nameValid || pending}>\n {pending ? 'Saving…' : editing ? 'Save changes' : 'Create portfolio'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ============================================================================\n// Holding create / edit dialog (scoped to a portfolio)\n// ============================================================================\n\ninterface HoldingFormState {\n accountId: string;\n symbol: string;\n name: string;\n assetClass: BraAssetClass;\n quantity: string;\n lastPrice: string;\n costBasis: string;\n}\n\nfunction emptyHoldingForm(): HoldingFormState {\n return {\n accountId: '',\n symbol: '',\n name: '',\n assetClass: 'EQUITY',\n quantity: '0',\n lastPrice: '0',\n costBasis: '0',\n };\n}\n\nfunction HoldingFormDialog({\n open,\n onOpenChange,\n portfolioId,\n editing,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n portfolioId: string;\n editing: BraHolding | null;\n}) {\n const createHolding = useCreateHolding();\n const updateHolding = useUpdateHolding();\n\n const [form, setForm] = useState<HoldingFormState>(emptyHoldingForm);\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const targetKey = editing?.id ?? 'new';\n if (open && seededFor !== targetKey) {\n setSeededFor(targetKey);\n setForm(\n editing\n ? {\n accountId: editing.accountId,\n symbol: editing.symbol,\n name: editing.name ?? '',\n assetClass: editing.assetClass,\n quantity: String(editing.quantity),\n lastPrice: String(editing.lastPrice),\n costBasis: String(editing.costBasis),\n }\n : emptyHoldingForm()\n );\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const pending = createHolding.isPending || updateHolding.isPending;\n const symbolValid = form.symbol.trim().length > 0;\n const accountValid = editing ? true : form.accountId.trim().length > 0;\n const canSubmit = symbolValid && accountValid && !pending;\n\n const handleSubmit = () => {\n if (!symbolValid || !accountValid) return;\n if (editing) {\n updateHolding.mutate(\n {\n id: editing.id,\n input: {\n name: form.name.trim() || null,\n assetClass: form.assetClass,\n quantity: toNumber(form.quantity),\n lastPrice: toNumber(form.lastPrice),\n costBasis: toNumber(form.costBasis),\n },\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Holding saved');\n onOpenChange(false);\n },\n }\n );\n } else {\n createHolding.mutate(\n {\n accountId: form.accountId,\n portfolioId,\n symbol: form.symbol.trim().toUpperCase(),\n name: form.name.trim() || null,\n assetClass: form.assetClass,\n quantity: toNumber(form.quantity),\n lastPrice: toNumber(form.lastPrice),\n costBasis: toNumber(form.costBasis),\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Holding added');\n onOpenChange(false);\n },\n }\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{editing ? 'Edit holding' : 'Add holding'}</DialogTitle>\n <DialogDescription>\n {editing\n ? 'Update the position details for this holding.'\n : 'Add a position to this portfolio from one of your accounts.'}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n {!editing ? (\n <AccountPicker\n id=\"h-account\"\n value={form.accountId}\n onChange={(v) => setForm((f) => ({ ...f, accountId: v }))}\n placeholder=\"Select account\"\n />\n ) : null}\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-symbol\">Symbol</Label>\n <Input\n id=\"h-symbol\"\n value={form.symbol}\n onChange={(e) => setForm((f) => ({ ...f, symbol: e.target.value }))}\n placeholder=\"AAPL\"\n disabled={Boolean(editing)}\n aria-invalid={!symbolValid}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-class\">Asset class</Label>\n <Select\n value={form.assetClass}\n onValueChange={(v) => setForm((f) => ({ ...f, assetClass: v as BraAssetClass }))}\n >\n <SelectTrigger id=\"h-class\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ASSET_CLASSES.map((c) => (\n <SelectItem key={c} value={c}>\n {prettyEnum(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-name\">Name</Label>\n <Input\n id=\"h-name\"\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Apple Inc.\"\n />\n </div>\n\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-qty\">Quantity</Label>\n <Input\n id=\"h-qty\"\n type=\"number\"\n step=\"any\"\n value={form.quantity}\n onChange={(e) => setForm((f) => ({ ...f, quantity: e.target.value }))}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-price\">Last price</Label>\n <Input\n id=\"h-price\"\n type=\"number\"\n step=\"any\"\n value={form.lastPrice}\n onChange={(e) => setForm((f) => ({ ...f, lastPrice: e.target.value }))}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-cost\">Cost basis</Label>\n <Input\n id=\"h-cost\"\n type=\"number\"\n step=\"any\"\n value={form.costBasis}\n onChange={(e) => setForm((f) => ({ ...f, costBasis: e.target.value }))}\n />\n </div>\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {pending ? 'Saving…' : editing ? 'Save changes' : 'Add holding'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n// ============================================================================\n// Portfolio detail sheet — holdings table + target vs actual allocation\n// ============================================================================\n\nfunction PortfolioDetailSheet({\n portfolioId,\n onOpenChange,\n}: {\n portfolioId: string | null;\n onOpenChange: (open: boolean) => void;\n}) {\n const { data: portfolio, isLoading, isError, refetch } = usePortfolio(portfolioId ?? undefined);\n // `braPortfolio.holdings` is a paginated connection (default page 50): reading\n // its first page would understate the totals below for any portfolio holding\n // more positions than that. Page through the full set instead, exactly as the\n // list cards do, so the sheet and the card can never disagree.\n const sheetHoldings = useAllHoldings(\n { portfolioId: portfolioId ?? undefined },\n { enabled: portfolioId !== null }\n );\n const deleteHolding = useDeleteHolding();\n\n const [holdingDialogOpen, setHoldingDialogOpen] = useState(false);\n const [editingHolding, setEditingHolding] = useState<BraHolding | null>(null);\n const [holdingToDelete, setHoldingToDelete] = useState<BraHolding | null>(null);\n\n const holdings = portfolioId ? sheetHoldings.data : [];\n const currency = portfolio?.baseCurrency ?? 'USD';\n const totalMarketValue = holdings.reduce((s, h) => s + h.marketValue, 0);\n const totalCostBasis = holdings.reduce((s, h) => s + h.costBasis, 0);\n const totalGain = totalMarketValue - totalCostBasis;\n const gainPct = totalCostBasis > 0 ? (totalGain / totalCostBasis) * 100 : 0;\n\n /** Live allocation by class in base-currency value — tone-keyed donut slices. */\n const actualDonut: DonutDatum[] = useMemo(() => {\n if (holdings.length === 0 || totalMarketValue <= 0) return [];\n const byClass = new Map<string, number>();\n holdings.forEach((h) =>\n byClass.set(h.assetClass, (byClass.get(h.assetClass) ?? 0) + h.marketValue)\n );\n return Array.from(byClass.entries())\n .map(([cls, mv]) => ({ label: prettyEnum(cls), value: mv, tone: toneForClass(cls) }))\n .sort((a, b) => b.value - a.value);\n }, [holdings, totalMarketValue]);\n\n /** Actual allocation as percentages, keyed by pretty class label, for drift comparison. */\n const actualPctByLabel = useMemo(() => {\n const map = new Map<string, number>();\n if (totalMarketValue <= 0) return map;\n holdings.forEach((h) =>\n map.set(\n prettyEnum(h.assetClass),\n (map.get(prettyEnum(h.assetClass)) ?? 0) + (h.marketValue / totalMarketValue) * 100\n )\n );\n return map;\n }, [holdings, totalMarketValue]);\n\n /** Target vs actual: each target row captioned with the live actual weight. */\n const targetVsActual: AllocationDatum[] = useMemo(() => {\n return parseTargetAllocation(portfolio?.targetAllocation).map((d) => {\n const actual = actualPctByLabel.get(d.label) ?? 0;\n return {\n label: d.label,\n value: d.value,\n tone: 'accent' as const,\n caption: `${formatPct(d.value, 0)} target · ${formatPct(actual, 0)} actual`,\n };\n });\n }, [portfolio?.targetAllocation, actualPctByLabel]);\n\n const openAddHolding = () => {\n setEditingHolding(null);\n setHoldingDialogOpen(true);\n };\n const openEditHolding = (h: BraHolding) => {\n setEditingHolding(h);\n setHoldingDialogOpen(true);\n };\n\n return (\n <Sheet open={portfolioId !== null} onOpenChange={onOpenChange}>\n <SheetContent className=\"flex w-full flex-col gap-0 overflow-y-auto p-0 sm:max-w-2xl\">\n {isLoading || (portfolioId !== null && sheetHoldings.isPending) ? (\n <div className=\"p-6\">\n <LoadingState rows={5} />\n </div>\n ) : isError || sheetHoldings.isError ? (\n <div className=\"p-6\">\n <ErrorState\n onRetry={() => {\n void refetch();\n void sheetHoldings.refetch();\n }}\n />\n </div>\n ) : !portfolio ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"Portfolio not found\"\n description=\"This portfolio may have been archived or removed.\"\n />\n </div>\n ) : (\n <>\n <SheetHeader className=\"border-b border-border-subtle p-6\">\n <div className=\"flex items-center gap-2\">\n <SheetTitle className=\"text-xl\">{portfolio.name}</SheetTitle>\n <Badge variant={statusBadgeVariant(portfolio.status)}>\n {prettyEnum(portfolio.status)}\n </Badge>\n </div>\n <SheetDescription>\n {portfolio.description?.trim()\n ? portfolio.description\n : `${portfolio.baseCurrency} · Drift threshold ${formatPct(portfolio.driftThresholdPct)}`}\n </SheetDescription>\n </SheetHeader>\n\n <div className=\"space-y-6 p-6\">\n {/* Hero KPIs — market value carries an emerald glow + live gain delta */}\n <FadeIn className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatTile\n label=\"Market value\"\n value={formatMoney(totalMarketValue, currency)}\n glow\n />\n <StatTile\n label=\"Unrealized gain\"\n value={formatMoney(totalGain, currency)}\n delta={totalCostBasis > 0 ? gainPct : undefined}\n deltaTone={totalGain > 0 ? 'positive' : totalGain < 0 ? 'negative' : 'neutral'}\n />\n <StatTile label=\"Holdings\" value={String(holdings.length)} />\n <StatTile label=\"Drift threshold\" value={formatPct(portfolio.driftThresholdPct)} />\n </FadeIn>\n\n {/* Target vs actual allocation — live donut + drift-aware target bars */}\n <FadeIn index={1}>\n <GlassCard glow className=\"space-y-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Target vs actual allocation\n </h3>\n <Badge variant=\"outline\">{currency}</Badge>\n </div>\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Actual — by market value\n </p>\n {actualDonut.length > 0 ? (\n <DonutChart\n data={actualDonut}\n centerValue={formatCompact(totalMarketValue, currency)}\n centerLabel=\"Market value\"\n valueFormatter={(v) => formatMoney(v, currency)}\n />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n Add holdings to see the live allocation.\n </p>\n )}\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Target — vs live actual\n </p>\n {targetVsActual.length > 0 ? (\n <AllocationBars data={targetVsActual} />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No target allocation set for this portfolio.\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </FadeIn>\n\n {/* Tags */}\n <Card>\n <CardContent className=\"p-4\">\n <TagField entityType=\"brainyrich-portfolio\" entityId={portfolio.id} />\n </CardContent>\n </Card>\n\n {/* Holdings table */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <div>\n <CardTitle className=\"text-base\">Holdings</CardTitle>\n <CardDescription>Positions held in this portfolio</CardDescription>\n </div>\n <Button size=\"sm\" onClick={openAddHolding}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n Add holding\n </Button>\n </CardHeader>\n <CardContent>\n {holdings.length === 0 ? (\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No holdings yet\"\n description=\"Add positions to start tracking this portfolio.\"\n action={\n <Button size=\"sm\" onClick={openAddHolding}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n Add holding\n </Button>\n }\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead>Class</TableHead>\n <TableHead className=\"text-right\">Qty</TableHead>\n <TableHead className=\"text-right\">Price</TableHead>\n <TableHead className=\"text-right\">Market value</TableHead>\n <TableHead className=\"text-right\">Gain</TableHead>\n <TableHead className=\"w-10\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {holdings.map((h) => {\n const gain = h.marketValue - h.costBasis;\n const gainPctRow = h.costBasis > 0 ? (gain / h.costBasis) * 100 : 0;\n return (\n <TableRow\n key={h.id}\n className=\"transition-colors motion-safe:hover:bg-bg-sunken/60\"\n >\n <TableCell>\n <div className=\"font-semibold text-text-primary\">{h.symbol}</div>\n {h.name ? (\n <div className=\"text-xs text-text-secondary\">{h.name}</div>\n ) : null}\n </TableCell>\n <TableCell>\n <Badge variant=\"outline\">{prettyEnum(h.assetClass)}</Badge>\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {h.quantity}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {formatMoney(h.lastPrice, h.currency)}\n </TableCell>\n <TableCell className=\"text-right font-medium tabular-nums\">\n {formatMoney(h.marketValue, h.currency)}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n <GainValue value={gain} currency={h.currency} />\n {h.costBasis > 0 ? (\n <div className=\"text-xs text-text-secondary\">\n {gain > 0 ? '+' : ''}\n {formatPct(gainPctRow)}\n </div>\n ) : null}\n </TableCell>\n <TableCell className=\"text-right\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-10\"\n aria-label={`Actions for ${h.symbol}`}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>{h.symbol}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"gap-2\"\n onClick={() => openEditHolding(h)}\n >\n <Pencil className=\"size-4\" aria-hidden=\"true\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuItem\n className=\"gap-2 text-status-error-text focus:text-status-error-text\"\n onClick={() => setHoldingToDelete(h)}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Tags — real UniversalTag assignments keyed by entity */}\n <Card>\n <CardHeader className=\"pb-3\">\n <CardTitle className=\"text-sm\">Tags</CardTitle>\n <CardDescription>Organize and filter this portfolio.</CardDescription>\n </CardHeader>\n <CardContent>\n <TagField entityType=\"brainyrich-portfolio\" entityId={portfolio.id} hideLabel />\n </CardContent>\n </Card>\n\n {/* Discussion — threaded comments keyed by entity */}\n <Card>\n <CardContent className=\"pt-6\">\n <CommentThread entityType=\"brainyrich-portfolio\" entityId={portfolio.id} />\n </CardContent>\n </Card>\n </div>\n\n <HoldingFormDialog\n open={holdingDialogOpen}\n onOpenChange={setHoldingDialogOpen}\n portfolioId={portfolio.id}\n editing={editingHolding}\n />\n\n <ConfirmDialog\n open={holdingToDelete !== null}\n onOpenChange={(open) => {\n if (!open) setHoldingToDelete(null);\n }}\n title=\"Delete holding?\"\n description={\n holdingToDelete\n ? `Remove ${holdingToDelete.symbol} from this portfolio. This cannot be undone.`\n : undefined\n }\n confirmLabel=\"Delete\"\n pendingLabel=\"Deleting…\"\n destructive\n pending={deleteHolding.isPending}\n onConfirm={() => {\n if (!holdingToDelete) return;\n deleteHolding.mutate(holdingToDelete.id, {\n onSuccess: () => setHoldingToDelete(null),\n });\n }}\n />\n </>\n )}\n </SheetContent>\n </Sheet>\n );\n}\n\n// ============================================================================\n// Drift meter — |drift| measured against this portfolio's own threshold\n// ============================================================================\n\n/**\n * The bar reads as \"how close this portfolio is to breaching its OWN configured\n * `driftThresholdPct`\": full means at or past the threshold. It is not a share\n * of 100 points, so a 6.4-point drift on a 5-point threshold and a 1.1-point\n * drift on a 1-point threshold both read as breached, which is what they are.\n */\nfunction DriftMeter({\n driftPct,\n thresholdPct,\n assetClass,\n}: {\n driftPct: number;\n thresholdPct: number;\n assetClass: string | null;\n}) {\n const magnitude = Math.abs(driftPct);\n const breached = thresholdPct > 0 && magnitude > thresholdPct;\n const nearing = !breached && thresholdPct > 0 && magnitude >= thresholdPct * 0.75;\n const width = thresholdPct > 0 ? Math.min(100, (magnitude / thresholdPct) * 100) : 0;\n const fill = breached\n ? 'bg-status-error-bg'\n : nearing\n ? 'bg-status-warning-bg'\n : 'bg-accent-teal';\n const text = breached\n ? 'text-status-error-text'\n : nearing\n ? 'text-status-warning-text'\n : 'text-text-secondary';\n const label = assetClass\n ? `Widest drift: ${prettyEnum(assetClass)} ${driftPct > 0 ? 'over' : 'under'}-weight by ${formatPct(magnitude)}`\n : `Widest drift ${formatPct(magnitude)}`;\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-2 text-xs\">\n <span className=\"text-text-secondary\">Drift</span>\n <span className={`font-medium tabular-nums ${text}`}>\n {breached ? '⚠ ' : ''}\n {driftPct > 0 ? '+' : ''}\n {formatPct(driftPct)}\n </span>\n </div>\n <div\n className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={Math.round(width)}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={label}\n title={label}\n >\n <div className={`h-full rounded-full ${fill}`} style={{ width: `${width}%` }} />\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Portfolio card — value, unrealized gain, drift + allocation donut (list grid)\n// ============================================================================\n\nfunction PortfolioCard({\n portfolio: p,\n valuation,\n valuationError,\n index,\n onView,\n onEdit,\n onArchive,\n}: {\n portfolio: BraPortfolio;\n /** Undefined while the holdings set is still loading — never rendered as zero. */\n valuation: PortfolioValuation | undefined;\n valuationError: boolean;\n index: number;\n onView: () => void;\n onEdit: () => void;\n onArchive: () => void;\n}) {\n const donut = useMemo(() => targetAllocationDonut(p.targetAllocation), [p.targetAllocation]);\n const top = donut[0];\n const tags = p.tags ?? [];\n\n return (\n <FadeIn index={index} as=\"div\" className=\"h-full\">\n <Card className=\"flex h-full flex-col transition-shadow motion-safe:hover:shadow-[var(--shadow-pop-hover,var(--shadow-elevation-2))]\">\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 text-[var(--color-action-primary-text)]\">\n <Briefcase className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <CardTitle className=\"truncate text-base\">{p.name}</CardTitle>\n <Badge variant={statusBadgeVariant(p.status)}>{prettyEnum(p.status)}</Badge>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-9 shrink-0\"\n aria-label={`Actions for ${p.name}`}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>{p.name}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem className=\"gap-2\" onClick={onEdit}>\n <Pencil className=\"size-4\" aria-hidden=\"true\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuItem\n className=\"gap-2 text-status-error-text focus:text-status-error-text\"\n disabled={p.status === 'ARCHIVED'}\n onClick={onArchive}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n Archive\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {p.description?.trim() ? (\n <CardDescription className=\"line-clamp-2\">{p.description}</CardDescription>\n ) : null}\n </CardHeader>\n\n <CardContent className=\"flex flex-1 flex-col justify-between gap-4\">\n {/* Live valuation — this portfolio's own holdings, rolled up */}\n {valuationError ? (\n <p className=\"text-sm text-text-secondary\">\n Valuation unavailable — positions could not be loaded.\n </p>\n ) : !valuation ? (\n <div className=\"space-y-2\" aria-busy=\"true\">\n <div className=\"h-7 w-32 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-1.5 w-full animate-pulse rounded-full bg-bg-sunken\" />\n </div>\n ) : valuation.holdingCount > 0 ? (\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-1\">\n <span className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {formatCompact(valuation.marketValue, p.baseCurrency)}\n </span>\n {valuation.gainPct !== null ? (\n <span\n className={`inline-flex items-center gap-1 text-sm font-medium tabular-nums ${deltaToneClass(valuation.unrealizedGain)}`}\n >\n {valuation.unrealizedGain > 0 ? (\n <ArrowUpRight className=\"size-3.5\" aria-hidden=\"true\" />\n ) : valuation.unrealizedGain < 0 ? (\n <ArrowDownRight className=\"size-3.5\" aria-hidden=\"true\" />\n ) : null}\n {valuation.unrealizedGain > 0 ? '+' : ''}\n {formatPct(valuation.gainPct)}\n </span>\n ) : null}\n <span className=\"text-xs text-text-secondary\">\n {valuation.holdingCount} holding{valuation.holdingCount === 1 ? '' : 's'}\n </span>\n </div>\n {valuation.maxDriftPct !== null ? (\n <DriftMeter\n driftPct={valuation.maxDriftPct}\n thresholdPct={p.driftThresholdPct}\n assetClass={valuation.driftAssetClass}\n />\n ) : (\n <p className=\"text-xs text-text-secondary\">\n Drift needs a target allocation on this portfolio.\n </p>\n )}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No holdings yet — add positions to value this portfolio.\n </p>\n )}\n\n <div className=\"grid grid-cols-[auto_1fr] items-center gap-4\">\n {donut.length > 0 ? (\n <div className=\"w-28 shrink-0\">\n <DonutChart\n data={donut}\n legend={false}\n height={112}\n centerValue={top ? formatPct(top.value, 0) : undefined}\n centerLabel={top?.label}\n valueFormatter={(v) => formatPct(v, 0)}\n />\n </div>\n ) : (\n <div className=\"flex size-28 shrink-0 items-center justify-center rounded-full border border-dashed border-border-subtle text-center text-[10px] leading-tight text-text-secondary\">\n No target\n <br />\n allocation\n </div>\n )}\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Badge variant=\"outline\">{p.baseCurrency}</Badge>\n <Badge variant=\"secondary\">Drift ≤ {formatPct(p.driftThresholdPct)}</Badge>\n </div>\n {donut.length > 0 ? (\n <ul className=\"space-y-1\" aria-label=\"Target allocation\">\n {donut.slice(0, 3).map((d) => (\n <li key={d.label} className=\"flex items-center gap-1.5 text-xs\">\n <span className=\"truncate text-text-secondary\">{d.label}</span>\n <span className=\"ml-auto font-medium tabular-nums text-text-primary\">\n {formatPct(d.value, 0)}\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n {tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[10px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n <Button variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={onView}>\n View holdings\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function PortfoliosPage() {\n useTrackBrainyRichPageView('brainyrich.portfolios.viewed');\n\n const {\n items: portfolios,\n hasMore,\n loadMore,\n isLoading,\n isFetchingNextPage,\n isError,\n refetch,\n } = usePagedPortfolios();\n const archivePortfolio = useArchivePortfolio();\n\n // Every visible holding, fully paged, in ONE request chain — grouped by\n // `portfolioId` below so each card can show its real value/gain/drift without\n // a per-portfolio round trip. `holdings.isPending` gates the derived numbers\n // so a card never briefly claims \"no holdings\" while the set is still loading.\n const holdingsQuery = useAllHoldings();\n const holdingsByPortfolio = useMemo(() => {\n const map = new Map<string, BraHolding[]>();\n for (const h of holdingsQuery.data) {\n if (!h.portfolioId) continue;\n const bucket = map.get(h.portfolioId);\n if (bucket) bucket.push(h);\n else map.set(h.portfolioId, [h]);\n }\n return map;\n }, [holdingsQuery.data]);\n\n const valuations = useMemo(() => {\n const map = new Map<string, PortfolioValuation>();\n for (const p of portfolios) {\n map.set(p.id, valuePortfolio(holdingsByPortfolio.get(p.id) ?? [], p.targetAllocation));\n }\n return map;\n }, [portfolios, holdingsByPortfolio]);\n\n const [formOpen, setFormOpen] = useState(false);\n const [editingPortfolio, setEditingPortfolio] = useState<BraPortfolio | null>(null);\n const [detailId, setDetailId] = useState<string | null>(null);\n const [portfolioToArchive, setPortfolioToArchive] = useState<BraPortfolio | null>(null);\n\n const activeCount = portfolios.filter((p) => p.status === 'ACTIVE').length;\n const draftCount = portfolios.filter((p) => p.status === 'DRAFT').length;\n const avgDrift =\n portfolios.length > 0\n ? portfolios.reduce((s, p) => s + p.driftThresholdPct, 0) / portfolios.length\n : 0;\n\n /**\n * Portfolio market value totalled PER BASE CURRENCY. Holdings carry no FX\n * conversion anywhere in this product (the service sums `quantity * lastPrice`\n * unconverted too), so a single cross-currency \"total value\" would be a\n * fabricated number — `braNetWorthSummary` is the FX-aware rollup.\n */\n const totalsByCurrency = useMemo(() => {\n const map = new Map<string, number>();\n for (const p of portfolios) {\n const v = valuations.get(p.id);\n if (!v || v.holdingCount === 0) continue;\n map.set(p.baseCurrency, (map.get(p.baseCurrency) ?? 0) + v.marketValue);\n }\n return map;\n }, [portfolios, valuations]);\n\n const valuedCurrencies = Array.from(totalsByCurrency.keys()).sort();\n const soleCurrency = valuedCurrencies.length === 1 ? valuedCurrencies[0] : undefined;\n /** Portfolios whose widest asset-class drift exceeds their own threshold. */\n const breachedCount = portfolios.filter((p) => {\n const drift = valuations.get(p.id)?.maxDriftPct;\n if (drift === undefined || drift === null || p.driftThresholdPct <= 0) return false;\n return Math.abs(drift) > p.driftThresholdPct;\n }).length;\n\n const openCreate = () => {\n setEditingPortfolio(null);\n setFormOpen(true);\n };\n const openEdit = (p: BraPortfolio) => {\n setEditingPortfolio(p);\n setFormOpen(true);\n };\n\n return (\n <div data-tour=\"brainyrich-page-portfolios\" className=\"contents\">\n <SectionShell\n title=\"Portfolios\"\n description=\"Organize holdings into strategies and keep allocations on target.\"\n badges={portfolios.length > 0 ? [`${portfolios.length} total`] : []}\n actions={\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n New portfolio\n </Button>\n }\n >\n {isLoading ? (\n <LoadingState rows={4} />\n ) : isError ? (\n <ErrorState onRetry={() => refetch()} />\n ) : portfolios.length === 0 ? (\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No portfolios yet\"\n description=\"Create your first portfolio to start organizing holdings and tracking drift.\"\n action={\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n New portfolio\n </Button>\n }\n />\n ) : (\n <>\n <FadeIn className=\"grid grid-cols-2 gap-3 lg:grid-cols-5\">\n <StatTile label=\"Portfolios\" value={String(portfolios.length)} glow />\n <StatTile\n label={soleCurrency ? `Total value (${soleCurrency})` : 'Total value'}\n value={\n holdingsQuery.isPending\n ? '…'\n : holdingsQuery.isError\n ? '—'\n : soleCurrency\n ? formatCompact(totalsByCurrency.get(soleCurrency) ?? 0, soleCurrency)\n : valuedCurrencies.length === 0\n ? '—'\n : `${valuedCurrencies.length} currencies`\n }\n />\n <StatTile label=\"Active\" value={String(activeCount)} />\n <StatTile label=\"Drafts\" value={String(draftCount)} />\n <StatTile\n label=\"Past drift threshold\"\n value={\n holdingsQuery.isPending\n ? '…'\n : holdingsQuery.isError\n ? '—'\n : String(breachedCount)\n }\n />\n </FadeIn>\n\n {/* Un-converted per-currency totals — never merged into one figure */}\n {!holdingsQuery.isPending && !holdingsQuery.isError && valuedCurrencies.length > 1 ? (\n <p className=\"text-xs text-text-secondary\">\n Market value is not FX-converted:{' '}\n {valuedCurrencies\n .map((c) => formatCompact(totalsByCurrency.get(c) ?? 0, c))\n .join(' · ')}\n . Average drift threshold {formatPct(avgDrift)}.\n </p>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n Average drift threshold across these portfolios: {formatPct(avgDrift)}.\n </p>\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {portfolios.map((p, i) => (\n <PortfolioCard\n key={p.id}\n portfolio={p}\n valuation={holdingsQuery.isPending ? undefined : valuations.get(p.id)}\n valuationError={holdingsQuery.isError}\n index={i}\n onView={() => setDetailId(p.id)}\n onEdit={() => openEdit(p)}\n onArchive={() => setPortfolioToArchive(p)}\n />\n ))}\n </div>\n\n <LoadMore hasMore={hasMore} onClick={loadMore} loading={isFetchingNextPage} />\n </>\n )}\n\n <PortfolioFormDialog\n open={formOpen}\n onOpenChange={setFormOpen}\n editing={editingPortfolio}\n />\n\n <PortfolioDetailSheet\n portfolioId={detailId}\n onOpenChange={(open) => {\n if (!open) setDetailId(null);\n }}\n />\n\n <ConfirmDialog\n open={portfolioToArchive !== null}\n onOpenChange={(open) => {\n if (!open) setPortfolioToArchive(null);\n }}\n title=\"Archive portfolio?\"\n description={\n portfolioToArchive\n ? `Archive “${portfolioToArchive.name}”. It will move to archived status and stop appearing in active views.`\n : undefined\n }\n confirmLabel=\"Archive\"\n pendingLabel=\"Archiving…\"\n destructive\n pending={archivePortfolio.isPending}\n onConfirm={() => {\n if (!portfolioToArchive) return;\n archivePortfolio.mutate(portfolioToArchive.id, {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio archived');\n setPortfolioToArchive(null);\n },\n });\n }}\n />\n </SectionShell>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PortfoliosPage.js","names":[],"sources":["../../../src/brainyrich/pages/PortfoliosPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownRight,\n ArrowUpRight,\n Briefcase,\n MoreHorizontal,\n Pencil,\n Plus,\n Trash2,\n} from 'lucide-react';\n\nimport {\n useAllHoldings,\n useArchivePortfolio,\n useCreateHolding,\n useCreatePortfolio,\n useDeleteHolding,\n useHouseholds,\n useModelPortfolios,\n usePagedPortfolios,\n usePortfolio,\n useUpdateHolding,\n useUpdatePortfolio,\n} from '../hooks/useBrainyRichApi';\nimport {\n AccountPicker,\n ConfirmDialog,\n DonutChart,\n FadeIn,\n GlassCard,\n LoadMore,\n StatTile,\n type DonutDatum,\n type VizTone,\n} from '../components';\nimport { CommentThread } from '../components/reuse/CommentThread';\nimport { TagField } from '../components/reuse/TagField';\nimport { hapticSuccess, showToast } from '../utils/nativeControls';\nimport { useTrackBrainyRichPageView } from '../hooks/useBrainyRichAnalytics';\nimport type { BraAssetClass, BraHolding, BraPortfolio, BraPortfolioStatus } from '../types/bra';\nimport {\n AllocationBars,\n EmptyState,\n ErrorState,\n LoadingState,\n SectionShell,\n deltaToneClass,\n formatCompact,\n formatMoney,\n formatPct,\n statusBadgeVariant,\n type AllocationDatum,\n} from './_shared';\n\nconst ASSET_CLASSES: BraAssetClass[] = [\n 'EQUITY',\n 'FIXED_INCOME',\n 'CASH',\n 'REAL_ESTATE',\n 'COMMODITY',\n 'CRYPTO',\n 'ALTERNATIVE',\n];\n\nconst PORTFOLIO_STATUSES: BraPortfolioStatus[] = ['DRAFT', 'ACTIVE', 'ARCHIVED'];\n\nconst NONE = '__none__';\n\nconst prettyEnum = (value: string): string =>\n value\n .toLowerCase()\n .split('_')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n\nconst toNumber = (value: string): number | undefined => {\n if (value.trim() === '') return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n};\n\n/** Stable, semantic tone per asset class so donut/legend colours read consistently. */\nconst ASSET_TONE: Record<string, VizTone> = {\n EQUITY: 'brand',\n FIXED_INCOME: 'blue',\n CASH: 'teal',\n REAL_ESTATE: 'purple',\n COMMODITY: 'warning',\n CRYPTO: 'indigo',\n ALTERNATIVE: 'info',\n};\n\nconst toneForClass = (raw: string): VizTone => ASSET_TONE[raw.toUpperCase()] ?? 'neutral';\n\ninterface TargetAllocationEntry {\n assetClass: string;\n targetPct: number;\n}\n\n/**\n * Read a portfolio's `targetAllocation` JSON scalar as `{ assetClass, targetPct }`\n * entries.\n *\n * The service persists and reads this as an ARRAY of `{ assetClass, targetPct }`\n * objects — `braCreatePortfolio` defaults it to `[]`, and the drift engine's\n * loader (`intelligence/lib/risk-data.ts#parseTargetAllocation`) discards\n * anything that isn't an array of those objects. Rows written before that shape\n * settled, as a flat `{ EQUITY: 60 }` map, are still read here so they don't\n * silently render as \"no target allocation\".\n */\nfunction readTargetAllocation(raw: unknown): TargetAllocationEntry[] {\n if (Array.isArray(raw)) {\n return raw.flatMap((entry) => {\n if (!entry || typeof entry !== 'object') return [];\n const { assetClass, targetPct } = entry as Record<string, unknown>;\n if (typeof assetClass !== 'string') return [];\n const pct = Number(targetPct);\n return Number.isFinite(pct) ? [{ assetClass, targetPct: pct }] : [];\n });\n }\n if (raw && typeof raw === 'object') {\n return Object.entries(raw as Record<string, unknown>).flatMap(([assetClass, value]) => {\n const pct = Number(value);\n return Number.isFinite(pct) ? [{ assetClass, targetPct: pct }] : [];\n });\n }\n return [];\n}\n\n/** Read a `targetAllocation` JSON blob as target-weight bars. */\nfunction parseTargetAllocation(raw: unknown): AllocationDatum[] {\n return readTargetAllocation(raw).map((d) => ({\n label: prettyEnum(d.assetClass),\n value: d.targetPct,\n tone: 'accent' as const,\n }));\n}\n\n/** Read a `targetAllocation` JSON blob as tone-keyed donut slices (percent values). */\nfunction targetAllocationDonut(raw: unknown): DonutDatum[] {\n return readTargetAllocation(raw)\n .filter((d) => d.targetPct > 0)\n .sort((a, b) => b.targetPct - a.targetPct)\n .map((d) => ({\n label: prettyEnum(d.assetClass),\n value: d.targetPct,\n tone: toneForClass(d.assetClass),\n }));\n}\n\n// ============================================================================\n// Portfolio valuation — real holdings rolled up per portfolio\n// ============================================================================\n\nexport interface PortfolioValuation {\n /** Σ holding.marketValue (server-derived `quantity * lastPrice`). */\n marketValue: number;\n /** Σ holding.costBasis. */\n costBasis: number;\n unrealizedGain: number;\n /** null when there is no cost basis to divide by. */\n gainPct: number | null;\n holdingCount: number;\n /** Largest absolute asset-class drift in points; null without a target or holdings. */\n maxDriftPct: number | null;\n /** The asset class carrying `maxDriftPct`. */\n driftAssetClass: string | null;\n}\n\nconst EMPTY_VALUATION: PortfolioValuation = {\n marketValue: 0,\n costBasis: 0,\n unrealizedGain: 0,\n gainPct: null,\n holdingCount: 0,\n maxDriftPct: null,\n driftAssetClass: null,\n};\n\n/**\n * Roll a portfolio's own holdings into value, unrealized gain and allocation\n * drift.\n *\n * Drift mirrors the service's deterministic engine exactly\n * (`intelligence/lib/risk-engine.ts`): `driftPct = actualAssetClassPct -\n * targetAssetClassPct`, over the union of the classes actually held and the\n * classes targeted, comparing the `assetClass` strings verbatim — the engine\n * does no case folding, so neither do we, or this card would report a different\n * number than the DRIFT insight raised against the same portfolio.\n *\n * Values are summed unconverted, which is also what the engine does: it never\n * applies an FX rate to `quantity * lastPrice`. Cross-portfolio totals are\n * therefore only ever presented per base currency.\n */\nfunction valuePortfolio(holdings: BraHolding[], targetAllocation: unknown): PortfolioValuation {\n if (holdings.length === 0) return EMPTY_VALUATION;\n\n let marketValue = 0;\n let costBasis = 0;\n const valueByClass = new Map<string, number>();\n for (const h of holdings) {\n marketValue += h.marketValue;\n costBasis += h.costBasis;\n valueByClass.set(h.assetClass, (valueByClass.get(h.assetClass) ?? 0) + h.marketValue);\n }\n\n const unrealizedGain = marketValue - costBasis;\n const targets = readTargetAllocation(targetAllocation);\n\n let maxDriftPct: number | null = null;\n let driftAssetClass: string | null = null;\n if (targets.length > 0 && marketValue > 0) {\n const classes = new Set<string>([...valueByClass.keys(), ...targets.map((t) => t.assetClass)]);\n for (const assetClass of classes) {\n const actualPct = ((valueByClass.get(assetClass) ?? 0) / marketValue) * 100;\n const targetPct = targets.find((t) => t.assetClass === assetClass)?.targetPct ?? 0;\n const driftPct = actualPct - targetPct;\n if (maxDriftPct === null || Math.abs(driftPct) > Math.abs(maxDriftPct)) {\n maxDriftPct = driftPct;\n driftAssetClass = assetClass;\n }\n }\n }\n\n return {\n marketValue,\n costBasis,\n unrealizedGain,\n gainPct: costBasis > 0 ? (unrealizedGain / costBasis) * 100 : null,\n holdingCount: holdings.length,\n maxDriftPct,\n driftAssetClass,\n };\n}\n\n// ============================================================================\n// Gain cell — signed money with icon + semantic color (color never sole signal)\n// ============================================================================\n\nfunction GainValue({ value, currency }: { value: number; currency: string }) {\n const positive = value > 0;\n const negative = value < 0;\n const tone = positive\n ? 'text-status-success-text'\n : negative\n ? 'text-status-error-text'\n : 'text-text-secondary';\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : null;\n return (\n <span className={`inline-flex items-center justify-end gap-1 font-medium ${tone}`}>\n {Icon ? <Icon className=\"size-3.5\" aria-hidden=\"true\" /> : null}\n {positive ? '+' : ''}\n {formatMoney(value, currency)}\n </span>\n );\n}\n\n// ============================================================================\n// Portfolio create / edit dialog\n// ============================================================================\n\ninterface PortfolioFormState {\n name: string;\n baseCurrency: string;\n description: string;\n driftThresholdPct: string;\n status: BraPortfolioStatus;\n modelPortfolioId: string;\n householdId: string;\n}\n\nfunction emptyForm(): PortfolioFormState {\n return {\n name: '',\n baseCurrency: 'USD',\n description: '',\n driftThresholdPct: '5',\n status: 'DRAFT',\n modelPortfolioId: NONE,\n householdId: NONE,\n };\n}\n\nfunction PortfolioFormDialog({\n open,\n onOpenChange,\n editing,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n editing: BraPortfolio | null;\n}) {\n const { data: models } = useModelPortfolios();\n const { data: households } = useHouseholds();\n const createPortfolio = useCreatePortfolio();\n const updatePortfolio = useUpdatePortfolio();\n\n const [form, setForm] = useState<PortfolioFormState>(emptyForm);\n // Re-seed the form whenever the dialog target changes.\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const targetKey = editing?.id ?? 'new';\n if (open && seededFor !== targetKey) {\n setSeededFor(targetKey);\n setForm(\n editing\n ? {\n name: editing.name,\n baseCurrency: editing.baseCurrency,\n description: editing.description ?? '',\n driftThresholdPct: String(editing.driftThresholdPct),\n status: editing.status,\n modelPortfolioId: editing.modelPortfolioId ?? NONE,\n householdId: editing.householdId ?? NONE,\n }\n : emptyForm()\n );\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const pending = createPortfolio.isPending || updatePortfolio.isPending;\n const nameValid = form.name.trim().length > 0;\n\n const handleSubmit = () => {\n if (!nameValid) return;\n const drift = toNumber(form.driftThresholdPct);\n const modelPortfolioId = form.modelPortfolioId === NONE ? null : form.modelPortfolioId;\n const householdId = form.householdId === NONE ? null : form.householdId;\n\n if (editing) {\n updatePortfolio.mutate(\n {\n id: editing.id,\n input: {\n name: form.name.trim(),\n baseCurrency: form.baseCurrency.trim() || 'USD',\n description: form.description.trim() || null,\n driftThresholdPct: drift,\n status: form.status,\n modelPortfolioId,\n },\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio saved');\n onOpenChange(false);\n },\n }\n );\n } else {\n createPortfolio.mutate(\n {\n name: form.name.trim(),\n baseCurrency: form.baseCurrency.trim() || 'USD',\n description: form.description.trim() || null,\n driftThresholdPct: drift,\n modelPortfolioId,\n householdId,\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio created');\n onOpenChange(false);\n },\n }\n );\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? 'Edit portfolio' : 'New portfolio'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {editing\n ? 'Update this portfolio’s strategy settings.'\n : 'Create an investment portfolio to organize and rebalance holdings.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-name\">Name</Label>\n <Input\n id=\"pf-name\"\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Growth Portfolio\"\n aria-invalid={!nameValid}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-currency\">Base currency</Label>\n <Input\n id=\"pf-currency\"\n value={form.baseCurrency}\n onChange={(e) =>\n setForm((f) => ({ ...f, baseCurrency: e.target.value.toUpperCase() }))\n }\n placeholder=\"USD\"\n maxLength={3}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-drift\">Drift threshold %</Label>\n <Input\n id=\"pf-drift\"\n type=\"number\"\n min={0}\n step=\"0.5\"\n value={form.driftThresholdPct}\n onChange={(e) => setForm((f) => ({ ...f, driftThresholdPct: e.target.value }))}\n />\n </div>\n </div>\n\n {editing ? (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-status\">Status</Label>\n <Select\n value={form.status}\n onValueChange={(v) => setForm((f) => ({ ...f, status: v as BraPortfolioStatus }))}\n >\n <SelectTrigger id=\"pf-status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PORTFOLIO_STATUSES.map((s) => (\n <SelectItem key={s} value={s}>\n {prettyEnum(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-model\">Model portfolio</Label>\n <Select\n value={form.modelPortfolioId}\n onValueChange={(v) => setForm((f) => ({ ...f, modelPortfolioId: v }))}\n >\n <SelectTrigger id=\"pf-model\">\n <SelectValue placeholder=\"None\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>None</SelectItem>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n {m.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {!editing ? (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-household\">Household</Label>\n <Select\n value={form.householdId}\n onValueChange={(v) => setForm((f) => ({ ...f, householdId: v }))}\n >\n <SelectTrigger id=\"pf-household\">\n <SelectValue placeholder=\"None\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE}>None</SelectItem>\n {households.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"pf-desc\">Description</Label>\n <Textarea\n id=\"pf-desc\"\n value={form.description}\n onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}\n placeholder=\"Optional notes about this strategy\"\n rows={3}\n />\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!nameValid || pending}>\n {pending ? 'Saving…' : editing ? 'Save changes' : 'Create portfolio'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Holding create / edit dialog (scoped to a portfolio)\n// ============================================================================\n\ninterface HoldingFormState {\n accountId: string;\n symbol: string;\n name: string;\n assetClass: BraAssetClass;\n quantity: string;\n lastPrice: string;\n costBasis: string;\n}\n\nfunction emptyHoldingForm(): HoldingFormState {\n return {\n accountId: '',\n symbol: '',\n name: '',\n assetClass: 'EQUITY',\n quantity: '0',\n lastPrice: '0',\n costBasis: '0',\n };\n}\n\nfunction HoldingFormDialog({\n open,\n onOpenChange,\n portfolioId,\n editing,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n portfolioId: string;\n editing: BraHolding | null;\n}) {\n const createHolding = useCreateHolding();\n const updateHolding = useUpdateHolding();\n\n const [form, setForm] = useState<HoldingFormState>(emptyHoldingForm);\n const [seededFor, setSeededFor] = useState<string | null>(null);\n const targetKey = editing?.id ?? 'new';\n if (open && seededFor !== targetKey) {\n setSeededFor(targetKey);\n setForm(\n editing\n ? {\n accountId: editing.accountId,\n symbol: editing.symbol,\n name: editing.name ?? '',\n assetClass: editing.assetClass,\n quantity: String(editing.quantity),\n lastPrice: String(editing.lastPrice),\n costBasis: String(editing.costBasis),\n }\n : emptyHoldingForm()\n );\n }\n if (!open && seededFor !== null) {\n setSeededFor(null);\n }\n\n const pending = createHolding.isPending || updateHolding.isPending;\n const symbolValid = form.symbol.trim().length > 0;\n const accountValid = editing ? true : form.accountId.trim().length > 0;\n const canSubmit = symbolValid && accountValid && !pending;\n\n const handleSubmit = () => {\n if (!symbolValid || !accountValid) return;\n if (editing) {\n updateHolding.mutate(\n {\n id: editing.id,\n input: {\n name: form.name.trim() || null,\n assetClass: form.assetClass,\n quantity: toNumber(form.quantity),\n lastPrice: toNumber(form.lastPrice),\n costBasis: toNumber(form.costBasis),\n },\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Holding saved');\n onOpenChange(false);\n },\n }\n );\n } else {\n createHolding.mutate(\n {\n accountId: form.accountId,\n portfolioId,\n symbol: form.symbol.trim().toUpperCase(),\n name: form.name.trim() || null,\n assetClass: form.assetClass,\n quantity: toNumber(form.quantity),\n lastPrice: toNumber(form.lastPrice),\n costBasis: toNumber(form.costBasis),\n },\n {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Holding added');\n onOpenChange(false);\n },\n }\n );\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{editing ? 'Edit holding' : 'Add holding'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {editing\n ? 'Update the position details for this holding.'\n : 'Add a position to this portfolio from one of your accounts.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n {!editing ? (\n <AccountPicker\n id=\"h-account\"\n value={form.accountId}\n onChange={(v) => setForm((f) => ({ ...f, accountId: v }))}\n placeholder=\"Select account\"\n />\n ) : null}\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-symbol\">Symbol</Label>\n <Input\n id=\"h-symbol\"\n value={form.symbol}\n onChange={(e) => setForm((f) => ({ ...f, symbol: e.target.value }))}\n placeholder=\"AAPL\"\n disabled={Boolean(editing)}\n aria-invalid={!symbolValid}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-class\">Asset class</Label>\n <Select\n value={form.assetClass}\n onValueChange={(v) => setForm((f) => ({ ...f, assetClass: v as BraAssetClass }))}\n >\n <SelectTrigger id=\"h-class\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ASSET_CLASSES.map((c) => (\n <SelectItem key={c} value={c}>\n {prettyEnum(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-name\">Name</Label>\n <Input\n id=\"h-name\"\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Apple Inc.\"\n />\n </div>\n\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-qty\">Quantity</Label>\n <Input\n id=\"h-qty\"\n type=\"number\"\n step=\"any\"\n value={form.quantity}\n onChange={(e) => setForm((f) => ({ ...f, quantity: e.target.value }))}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-price\">Last price</Label>\n <Input\n id=\"h-price\"\n type=\"number\"\n step=\"any\"\n value={form.lastPrice}\n onChange={(e) => setForm((f) => ({ ...f, lastPrice: e.target.value }))}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"h-cost\">Cost basis</Label>\n <Input\n id=\"h-cost\"\n type=\"number\"\n step=\"any\"\n value={form.costBasis}\n onChange={(e) => setForm((f) => ({ ...f, costBasis: e.target.value }))}\n />\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {pending ? 'Saving…' : editing ? 'Save changes' : 'Add holding'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Portfolio detail sheet — holdings table + target vs actual allocation\n// ============================================================================\n\nfunction PortfolioDetailSheet({\n portfolioId,\n onOpenChange,\n}: {\n portfolioId: string | null;\n onOpenChange: (open: boolean) => void;\n}) {\n const { data: portfolio, isLoading, isError, refetch } = usePortfolio(portfolioId ?? undefined);\n // `braPortfolio.holdings` is a paginated connection (default page 50): reading\n // its first page would understate the totals below for any portfolio holding\n // more positions than that. Page through the full set instead, exactly as the\n // list cards do, so the sheet and the card can never disagree.\n const sheetHoldings = useAllHoldings(\n { portfolioId: portfolioId ?? undefined },\n { enabled: portfolioId !== null }\n );\n const deleteHolding = useDeleteHolding();\n\n const [holdingDialogOpen, setHoldingDialogOpen] = useState(false);\n const [editingHolding, setEditingHolding] = useState<BraHolding | null>(null);\n const [holdingToDelete, setHoldingToDelete] = useState<BraHolding | null>(null);\n\n const holdings = useMemo(\n () => (portfolioId ? sheetHoldings.data : []),\n [portfolioId, sheetHoldings.data]\n );\n const currency = portfolio?.baseCurrency ?? 'USD';\n const totalMarketValue = holdings.reduce((s, h) => s + h.marketValue, 0);\n const totalCostBasis = holdings.reduce((s, h) => s + h.costBasis, 0);\n const totalGain = totalMarketValue - totalCostBasis;\n const gainPct = totalCostBasis > 0 ? (totalGain / totalCostBasis) * 100 : 0;\n\n /** Live allocation by class in base-currency value — tone-keyed donut slices. */\n const actualDonut: DonutDatum[] = useMemo(() => {\n if (holdings.length === 0 || totalMarketValue <= 0) return [];\n const byClass = new Map<string, number>();\n holdings.forEach((h) =>\n byClass.set(h.assetClass, (byClass.get(h.assetClass) ?? 0) + h.marketValue)\n );\n return Array.from(byClass.entries())\n .map(([cls, mv]) => ({ label: prettyEnum(cls), value: mv, tone: toneForClass(cls) }))\n .sort((a, b) => b.value - a.value);\n }, [holdings, totalMarketValue]);\n\n /** Actual allocation as percentages, keyed by pretty class label, for drift comparison. */\n const actualPctByLabel = useMemo(() => {\n const map = new Map<string, number>();\n if (totalMarketValue <= 0) return map;\n holdings.forEach((h) =>\n map.set(\n prettyEnum(h.assetClass),\n (map.get(prettyEnum(h.assetClass)) ?? 0) + (h.marketValue / totalMarketValue) * 100\n )\n );\n return map;\n }, [holdings, totalMarketValue]);\n\n /** Target vs actual: each target row captioned with the live actual weight. */\n const targetVsActual: AllocationDatum[] = useMemo(() => {\n return parseTargetAllocation(portfolio?.targetAllocation).map((d) => {\n const actual = actualPctByLabel.get(d.label) ?? 0;\n return {\n label: d.label,\n value: d.value,\n tone: 'accent' as const,\n caption: `${formatPct(d.value, 0)} target · ${formatPct(actual, 0)} actual`,\n };\n });\n }, [portfolio?.targetAllocation, actualPctByLabel]);\n\n const openAddHolding = () => {\n setEditingHolding(null);\n setHoldingDialogOpen(true);\n };\n const openEditHolding = (h: BraHolding) => {\n setEditingHolding(h);\n setHoldingDialogOpen(true);\n };\n\n return (\n <Sheet open={portfolioId !== null} onOpenChange={onOpenChange}>\n <SheetContent className=\"flex w-full flex-col gap-0 overflow-y-auto p-0 sm:max-w-2xl\">\n {isLoading || (portfolioId !== null && sheetHoldings.isPending) ? (\n <div className=\"p-6\">\n <LoadingState rows={5} />\n </div>\n ) : isError || sheetHoldings.isError ? (\n <div className=\"p-6\">\n <ErrorState\n onRetry={() => {\n void refetch();\n void sheetHoldings.refetch();\n }}\n />\n </div>\n ) : !portfolio ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"Portfolio not found\"\n description=\"This portfolio may have been archived or removed.\"\n />\n </div>\n ) : (\n <>\n <SheetHeader className=\"border-b border-border-subtle p-6\">\n <div className=\"flex items-center gap-2\">\n <SheetTitle className=\"text-xl\">{portfolio.name}</SheetTitle>\n <Badge variant={statusBadgeVariant(portfolio.status)}>\n {prettyEnum(portfolio.status)}\n </Badge>\n </div>\n <SheetDescription>\n {portfolio.description?.trim()\n ? portfolio.description\n : `${portfolio.baseCurrency} · Drift threshold ${formatPct(portfolio.driftThresholdPct)}`}\n </SheetDescription>\n </SheetHeader>\n\n <div className=\"space-y-6 p-6\">\n {/* Hero KPIs — market value carries an emerald glow + live gain delta */}\n <FadeIn className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatTile\n label=\"Market value\"\n value={formatMoney(totalMarketValue, currency)}\n glow\n />\n <StatTile\n label=\"Unrealized gain\"\n value={formatMoney(totalGain, currency)}\n delta={totalCostBasis > 0 ? gainPct : undefined}\n deltaTone={totalGain > 0 ? 'positive' : totalGain < 0 ? 'negative' : 'neutral'}\n />\n <StatTile label=\"Holdings\" value={String(holdings.length)} />\n <StatTile label=\"Drift threshold\" value={formatPct(portfolio.driftThresholdPct)} />\n </FadeIn>\n\n {/* Target vs actual allocation — live donut + drift-aware target bars */}\n <FadeIn index={1}>\n <GlassCard glow className=\"space-y-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Target vs actual allocation\n </h3>\n <Badge variant=\"outline\">{currency}</Badge>\n </div>\n <div className=\"grid gap-6 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Actual — by market value\n </p>\n {actualDonut.length > 0 ? (\n <DonutChart\n data={actualDonut}\n centerValue={formatCompact(totalMarketValue, currency)}\n centerLabel=\"Market value\"\n valueFormatter={(v) => formatMoney(v, currency)}\n />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n Add holdings to see the live allocation.\n </p>\n )}\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Target — vs live actual\n </p>\n {targetVsActual.length > 0 ? (\n <AllocationBars data={targetVsActual} />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No target allocation set for this portfolio.\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </FadeIn>\n\n {/* Tags */}\n <Card>\n <CardContent className=\"p-4\">\n <TagField entityType=\"brainyrich-portfolio\" entityId={portfolio.id} />\n </CardContent>\n </Card>\n\n {/* Holdings table */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <div>\n <CardTitle className=\"text-base\">Holdings</CardTitle>\n <CardDescription>Positions held in this portfolio</CardDescription>\n </div>\n <Button size=\"sm\" onClick={openAddHolding}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n Add holding\n </Button>\n </CardHeader>\n <CardContent>\n {holdings.length === 0 ? (\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No holdings yet\"\n description=\"Add positions to start tracking this portfolio.\"\n action={\n <Button size=\"sm\" onClick={openAddHolding}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n Add holding\n </Button>\n }\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead>Class</TableHead>\n <TableHead className=\"text-right\">Qty</TableHead>\n <TableHead className=\"text-right\">Price</TableHead>\n <TableHead className=\"text-right\">Market value</TableHead>\n <TableHead className=\"text-right\">Gain</TableHead>\n <TableHead className=\"w-10\" />\n </TableRow>\n </TableHeader>\n <TableBody>\n {holdings.map((h) => {\n const gain = h.marketValue - h.costBasis;\n const gainPctRow = h.costBasis > 0 ? (gain / h.costBasis) * 100 : 0;\n return (\n <TableRow\n key={h.id}\n className=\"transition-colors motion-safe:hover:bg-bg-sunken/60\"\n >\n <TableCell>\n <div className=\"font-semibold text-text-primary\">{h.symbol}</div>\n {h.name ? (\n <div className=\"text-xs text-text-secondary\">{h.name}</div>\n ) : null}\n </TableCell>\n <TableCell>\n <Badge variant=\"outline\">{prettyEnum(h.assetClass)}</Badge>\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {h.quantity}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {formatMoney(h.lastPrice, h.currency)}\n </TableCell>\n <TableCell className=\"text-right font-medium tabular-nums\">\n {formatMoney(h.marketValue, h.currency)}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n <GainValue value={gain} currency={h.currency} />\n {h.costBasis > 0 ? (\n <div className=\"text-xs text-text-secondary\">\n {gain > 0 ? '+' : ''}\n {formatPct(gainPctRow)}\n </div>\n ) : null}\n </TableCell>\n <TableCell className=\"text-right\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-10\"\n aria-label={`Actions for ${h.symbol}`}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>{h.symbol}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"gap-2\"\n onClick={() => openEditHolding(h)}\n >\n <Pencil className=\"size-4\" aria-hidden=\"true\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuItem\n className=\"gap-2 text-status-error-text focus:text-status-error-text\"\n onClick={() => setHoldingToDelete(h)}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Tags — real UniversalTag assignments keyed by entity */}\n <Card>\n <CardHeader className=\"pb-3\">\n <CardTitle className=\"text-sm\">Tags</CardTitle>\n <CardDescription>Organize and filter this portfolio.</CardDescription>\n </CardHeader>\n <CardContent>\n <TagField entityType=\"brainyrich-portfolio\" entityId={portfolio.id} hideLabel />\n </CardContent>\n </Card>\n\n {/* Discussion — threaded comments keyed by entity */}\n <Card>\n <CardContent className=\"pt-6\">\n <CommentThread entityType=\"brainyrich-portfolio\" entityId={portfolio.id} />\n </CardContent>\n </Card>\n </div>\n\n <HoldingFormDialog\n open={holdingDialogOpen}\n onOpenChange={setHoldingDialogOpen}\n portfolioId={portfolio.id}\n editing={editingHolding}\n />\n\n <ConfirmDialog\n open={holdingToDelete !== null}\n onOpenChange={(open) => {\n if (!open) setHoldingToDelete(null);\n }}\n title=\"Delete holding?\"\n description={\n holdingToDelete\n ? `Remove ${holdingToDelete.symbol} from this portfolio. This cannot be undone.`\n : undefined\n }\n confirmLabel=\"Delete\"\n pendingLabel=\"Deleting…\"\n destructive\n pending={deleteHolding.isPending}\n onConfirm={() => {\n if (!holdingToDelete) return;\n deleteHolding.mutate(holdingToDelete.id, {\n onSuccess: () => setHoldingToDelete(null),\n });\n }}\n />\n </>\n )}\n </SheetContent>\n </Sheet>\n );\n}\n\n// ============================================================================\n// Drift meter — |drift| measured against this portfolio's own threshold\n// ============================================================================\n\n/**\n * The bar reads as \"how close this portfolio is to breaching its OWN configured\n * `driftThresholdPct`\": full means at or past the threshold. It is not a share\n * of 100 points, so a 6.4-point drift on a 5-point threshold and a 1.1-point\n * drift on a 1-point threshold both read as breached, which is what they are.\n */\nfunction DriftMeter({\n driftPct,\n thresholdPct,\n assetClass,\n}: {\n driftPct: number;\n thresholdPct: number;\n assetClass: string | null;\n}) {\n const magnitude = Math.abs(driftPct);\n const breached = thresholdPct > 0 && magnitude > thresholdPct;\n const nearing = !breached && thresholdPct > 0 && magnitude >= thresholdPct * 0.75;\n const width = thresholdPct > 0 ? Math.min(100, (magnitude / thresholdPct) * 100) : 0;\n const fill = breached\n ? 'bg-status-error-bg'\n : nearing\n ? 'bg-status-warning-bg'\n : 'bg-accent-teal';\n const text = breached\n ? 'text-status-error-text'\n : nearing\n ? 'text-status-warning-text'\n : 'text-text-secondary';\n const label = assetClass\n ? `Widest drift: ${prettyEnum(assetClass)} ${driftPct > 0 ? 'over' : 'under'}-weight by ${formatPct(magnitude)}`\n : `Widest drift ${formatPct(magnitude)}`;\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-2 text-xs\">\n <span className=\"text-text-secondary\">Drift</span>\n <span className={`font-medium tabular-nums ${text}`}>\n {breached ? '⚠ ' : ''}\n {driftPct > 0 ? '+' : ''}\n {formatPct(driftPct)}\n </span>\n </div>\n <div\n className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={Math.round(width)}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={label}\n title={label}\n >\n <div className={`h-full rounded-full ${fill}`} style={{ width: `${width}%` }} />\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Portfolio card — value, unrealized gain, drift + allocation donut (list grid)\n// ============================================================================\n\nfunction PortfolioCard({\n portfolio: p,\n valuation,\n valuationError,\n index,\n onView,\n onEdit,\n onArchive,\n}: {\n portfolio: BraPortfolio;\n /** Undefined while the holdings set is still loading — never rendered as zero. */\n valuation: PortfolioValuation | undefined;\n valuationError: boolean;\n index: number;\n onView: () => void;\n onEdit: () => void;\n onArchive: () => void;\n}) {\n const donut = useMemo(() => targetAllocationDonut(p.targetAllocation), [p.targetAllocation]);\n const top = donut[0];\n const tags = p.tags ?? [];\n\n return (\n <FadeIn index={index} as=\"div\" className=\"h-full\">\n <Card className=\"flex h-full flex-col transition-shadow motion-safe:hover:shadow-[var(--shadow-pop-hover,var(--shadow-elevation-2))]\">\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 text-[var(--color-action-primary-text)]\">\n <Briefcase className=\"size-4\" aria-hidden=\"true\" />\n </span>\n <CardTitle className=\"truncate text-base\">{p.name}</CardTitle>\n <Badge variant={statusBadgeVariant(p.status)}>{prettyEnum(p.status)}</Badge>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-9 shrink-0\"\n aria-label={`Actions for ${p.name}`}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>{p.name}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem className=\"gap-2\" onClick={onEdit}>\n <Pencil className=\"size-4\" aria-hidden=\"true\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuItem\n className=\"gap-2 text-status-error-text focus:text-status-error-text\"\n disabled={p.status === 'ARCHIVED'}\n onClick={onArchive}\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n Archive\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {p.description?.trim() ? (\n <CardDescription className=\"line-clamp-2\">{p.description}</CardDescription>\n ) : null}\n </CardHeader>\n\n <CardContent className=\"flex flex-1 flex-col justify-between gap-4\">\n {/* Live valuation — this portfolio's own holdings, rolled up */}\n {valuationError ? (\n <p className=\"text-sm text-text-secondary\">\n Valuation unavailable — positions could not be loaded.\n </p>\n ) : !valuation ? (\n <div className=\"space-y-2\" aria-busy=\"true\">\n <div className=\"h-7 w-32 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-1.5 w-full animate-pulse rounded-full bg-bg-sunken\" />\n </div>\n ) : valuation.holdingCount > 0 ? (\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-1\">\n <span className=\"text-2xl font-semibold tabular-nums text-text-primary\">\n {formatCompact(valuation.marketValue, p.baseCurrency)}\n </span>\n {valuation.gainPct !== null ? (\n <span\n className={`inline-flex items-center gap-1 text-sm font-medium tabular-nums ${deltaToneClass(valuation.unrealizedGain)}`}\n >\n {valuation.unrealizedGain > 0 ? (\n <ArrowUpRight className=\"size-3.5\" aria-hidden=\"true\" />\n ) : valuation.unrealizedGain < 0 ? (\n <ArrowDownRight className=\"size-3.5\" aria-hidden=\"true\" />\n ) : null}\n {valuation.unrealizedGain > 0 ? '+' : ''}\n {formatPct(valuation.gainPct)}\n </span>\n ) : null}\n <span className=\"text-xs text-text-secondary\">\n {valuation.holdingCount} holding{valuation.holdingCount === 1 ? '' : 's'}\n </span>\n </div>\n {valuation.maxDriftPct !== null ? (\n <DriftMeter\n driftPct={valuation.maxDriftPct}\n thresholdPct={p.driftThresholdPct}\n assetClass={valuation.driftAssetClass}\n />\n ) : (\n <p className=\"text-xs text-text-secondary\">\n Drift needs a target allocation on this portfolio.\n </p>\n )}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No holdings yet — add positions to value this portfolio.\n </p>\n )}\n\n <div className=\"grid grid-cols-[auto_1fr] items-center gap-4\">\n {donut.length > 0 ? (\n <div className=\"w-28 shrink-0\">\n <DonutChart\n data={donut}\n legend={false}\n height={112}\n centerValue={top ? formatPct(top.value, 0) : undefined}\n centerLabel={top?.label}\n valueFormatter={(v) => formatPct(v, 0)}\n />\n </div>\n ) : (\n <div className=\"flex size-28 shrink-0 items-center justify-center rounded-full border border-dashed border-border-subtle text-center text-[10px] leading-tight text-text-secondary\">\n No target\n <br />\n allocation\n </div>\n )}\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Badge variant=\"outline\">{p.baseCurrency}</Badge>\n <Badge variant=\"secondary\">Drift ≤ {formatPct(p.driftThresholdPct)}</Badge>\n </div>\n {donut.length > 0 ? (\n <ul className=\"space-y-1\" aria-label=\"Target allocation\">\n {donut.slice(0, 3).map((d) => (\n <li key={d.label} className=\"flex items-center gap-1.5 text-xs\">\n <span className=\"truncate text-text-secondary\">{d.label}</span>\n <span className=\"ml-auto font-medium tabular-nums text-text-primary\">\n {formatPct(d.value, 0)}\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n {tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[10px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n <Button variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={onView}>\n View holdings\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function PortfoliosPage() {\n useTrackBrainyRichPageView('brainyrich.portfolios.viewed');\n\n const {\n items: portfolios,\n hasMore,\n loadMore,\n isLoading,\n isFetchingNextPage,\n isError,\n refetch,\n } = usePagedPortfolios();\n const archivePortfolio = useArchivePortfolio();\n\n // Every visible holding, fully paged, in ONE request chain — grouped by\n // `portfolioId` below so each card can show its real value/gain/drift without\n // a per-portfolio round trip. `holdings.isPending` gates the derived numbers\n // so a card never briefly claims \"no holdings\" while the set is still loading.\n const holdingsQuery = useAllHoldings();\n const holdingsByPortfolio = useMemo(() => {\n const map = new Map<string, BraHolding[]>();\n for (const h of holdingsQuery.data) {\n if (!h.portfolioId) continue;\n const bucket = map.get(h.portfolioId);\n if (bucket) bucket.push(h);\n else map.set(h.portfolioId, [h]);\n }\n return map;\n }, [holdingsQuery.data]);\n\n const valuations = useMemo(() => {\n const map = new Map<string, PortfolioValuation>();\n for (const p of portfolios) {\n map.set(p.id, valuePortfolio(holdingsByPortfolio.get(p.id) ?? [], p.targetAllocation));\n }\n return map;\n }, [portfolios, holdingsByPortfolio]);\n\n const [formOpen, setFormOpen] = useState(false);\n const [editingPortfolio, setEditingPortfolio] = useState<BraPortfolio | null>(null);\n const [detailId, setDetailId] = useState<string | null>(null);\n const [portfolioToArchive, setPortfolioToArchive] = useState<BraPortfolio | null>(null);\n\n const activeCount = portfolios.filter((p) => p.status === 'ACTIVE').length;\n const draftCount = portfolios.filter((p) => p.status === 'DRAFT').length;\n const avgDrift =\n portfolios.length > 0\n ? portfolios.reduce((s, p) => s + p.driftThresholdPct, 0) / portfolios.length\n : 0;\n\n /**\n * Portfolio market value totalled PER BASE CURRENCY. Holdings carry no FX\n * conversion anywhere in this product (the service sums `quantity * lastPrice`\n * unconverted too), so a single cross-currency \"total value\" would be a\n * fabricated number — `braNetWorthSummary` is the FX-aware rollup.\n */\n const totalsByCurrency = useMemo(() => {\n const map = new Map<string, number>();\n for (const p of portfolios) {\n const v = valuations.get(p.id);\n if (!v || v.holdingCount === 0) continue;\n map.set(p.baseCurrency, (map.get(p.baseCurrency) ?? 0) + v.marketValue);\n }\n return map;\n }, [portfolios, valuations]);\n\n const valuedCurrencies = Array.from(totalsByCurrency.keys()).sort();\n const soleCurrency = valuedCurrencies.length === 1 ? valuedCurrencies[0] : undefined;\n /** Portfolios whose widest asset-class drift exceeds their own threshold. */\n const breachedCount = portfolios.filter((p) => {\n const drift = valuations.get(p.id)?.maxDriftPct;\n if (drift === undefined || drift === null || p.driftThresholdPct <= 0) return false;\n return Math.abs(drift) > p.driftThresholdPct;\n }).length;\n\n const openCreate = () => {\n setEditingPortfolio(null);\n setFormOpen(true);\n };\n const openEdit = (p: BraPortfolio) => {\n setEditingPortfolio(p);\n setFormOpen(true);\n };\n\n return (\n <div data-tour=\"brainyrich-page-portfolios\" className=\"contents\">\n <SectionShell\n title=\"Portfolios\"\n description=\"Organize holdings into strategies and keep allocations on target.\"\n badges={portfolios.length > 0 ? [`${portfolios.length} total`] : []}\n actions={\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n New portfolio\n </Button>\n }\n >\n {isLoading ? (\n <LoadingState rows={4} />\n ) : isError ? (\n <ErrorState onRetry={() => refetch()} />\n ) : portfolios.length === 0 ? (\n <EmptyState\n icon={<Briefcase className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No portfolios yet\"\n description=\"Create your first portfolio to start organizing holdings and tracking drift.\"\n action={\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n New portfolio\n </Button>\n }\n />\n ) : (\n <>\n <FadeIn className=\"grid grid-cols-2 gap-3 lg:grid-cols-5\">\n <StatTile label=\"Portfolios\" value={String(portfolios.length)} glow />\n <StatTile\n label={soleCurrency ? `Total value (${soleCurrency})` : 'Total value'}\n value={\n holdingsQuery.isPending\n ? '…'\n : holdingsQuery.isError\n ? '—'\n : soleCurrency\n ? formatCompact(totalsByCurrency.get(soleCurrency) ?? 0, soleCurrency)\n : valuedCurrencies.length === 0\n ? '—'\n : `${valuedCurrencies.length} currencies`\n }\n />\n <StatTile label=\"Active\" value={String(activeCount)} />\n <StatTile label=\"Drafts\" value={String(draftCount)} />\n <StatTile\n label=\"Past drift threshold\"\n value={\n holdingsQuery.isPending\n ? '…'\n : holdingsQuery.isError\n ? '—'\n : String(breachedCount)\n }\n />\n </FadeIn>\n\n {/* Un-converted per-currency totals — never merged into one figure */}\n {!holdingsQuery.isPending && !holdingsQuery.isError && valuedCurrencies.length > 1 ? (\n <p className=\"text-xs text-text-secondary\">\n Market value is not FX-converted:{' '}\n {valuedCurrencies\n .map((c) => formatCompact(totalsByCurrency.get(c) ?? 0, c))\n .join(' · ')}\n . Average drift threshold {formatPct(avgDrift)}.\n </p>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n Average drift threshold across these portfolios: {formatPct(avgDrift)}.\n </p>\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {portfolios.map((p, i) => (\n <PortfolioCard\n key={p.id}\n portfolio={p}\n valuation={holdingsQuery.isPending ? undefined : valuations.get(p.id)}\n valuationError={holdingsQuery.isError}\n index={i}\n onView={() => setDetailId(p.id)}\n onEdit={() => openEdit(p)}\n onArchive={() => setPortfolioToArchive(p)}\n />\n ))}\n </div>\n\n <LoadMore hasMore={hasMore} onClick={loadMore} loading={isFetchingNextPage} />\n </>\n )}\n\n <PortfolioFormDialog\n open={formOpen}\n onOpenChange={setFormOpen}\n editing={editingPortfolio}\n />\n\n <PortfolioDetailSheet\n portfolioId={detailId}\n onOpenChange={(open) => {\n if (!open) setDetailId(null);\n }}\n />\n\n <ConfirmDialog\n open={portfolioToArchive !== null}\n onOpenChange={(open) => {\n if (!open) setPortfolioToArchive(null);\n }}\n title=\"Archive portfolio?\"\n description={\n portfolioToArchive\n ? `Archive “${portfolioToArchive.name}”. It will move to archived status and stop appearing in active views.`\n : undefined\n }\n confirmLabel=\"Archive\"\n pendingLabel=\"Archiving…\"\n destructive\n pending={archivePortfolio.isPending}\n onConfirm={() => {\n if (!portfolioToArchive) return;\n archivePortfolio.mutate(portfolioToArchive.id, {\n onSuccess: () => {\n hapticSuccess();\n void showToast('Portfolio archived');\n setPortfolioToArchive(null);\n },\n });\n }}\n />\n </SectionShell>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { CalendarClock as re, ChevronDown as ie, Download as ae, FileText as N,
|
|
|
8
8
|
import { useI18n as _e } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
9
|
import { useQuery as ve } from "@tanstack/react-query";
|
|
10
10
|
import { jsx as F, jsxs as I } from "react/jsx-runtime";
|
|
11
|
-
import { Badge as L, Button as R, Card as z, CardContent as B, CardDescription as V, CardHeader as H, CardTitle as U,
|
|
11
|
+
import { Badge as L, Button as R, Card as z, CardContent as B, CardDescription as V, CardHeader as H, CardTitle as U, DropdownMenu as ye, DropdownMenuContent as be, DropdownMenuItem as xe, DropdownMenuTrigger as Se, Input as Ce, Label as W, ResponsiveDialog as we, ResponsiveDialogClose as Te, ResponsiveDialogContent as Ee, ResponsiveDialogDescription as De, ResponsiveDialogFooter as Oe, ResponsiveDialogHeader as ke, ResponsiveDialogTitle as Ae, Select as je, SelectContent as Me, SelectItem as Ne, SelectTrigger as Pe, SelectValue as Fe, Skeleton as Ie, Table as G, TableBody as K, TableCell as q, TableHead as J, TableHeader as Y, TableRow as X, Tabs as Le, TabsList as Re, TabsTrigger as Z, Textarea as ze } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/brainyrich/pages/ReportsPage.tsx
|
|
13
13
|
var Be = {
|
|
14
14
|
EQUITY: "Equity",
|
|
@@ -272,7 +272,7 @@ function $e() {
|
|
|
272
272
|
return /* @__PURE__ */ I(ne, {
|
|
273
273
|
title: n("brainyrich.reports.title", "Reports & Statements"),
|
|
274
274
|
description: n("brainyrich.reports.description", "Generate statements directly from your live portfolio data — no fabricated schedules."),
|
|
275
|
-
actions: /* @__PURE__ */ I(
|
|
275
|
+
actions: /* @__PURE__ */ I(ye, { children: [/* @__PURE__ */ F(Se, {
|
|
276
276
|
asChild: !0,
|
|
277
277
|
children: /* @__PURE__ */ I(R, {
|
|
278
278
|
size: "sm",
|
|
@@ -290,15 +290,15 @@ function $e() {
|
|
|
290
290
|
})
|
|
291
291
|
]
|
|
292
292
|
})
|
|
293
|
-
}), /* @__PURE__ */ I(
|
|
293
|
+
}), /* @__PURE__ */ I(be, {
|
|
294
294
|
align: "end",
|
|
295
|
-
children: [/* @__PURE__ */ I(
|
|
295
|
+
children: [/* @__PURE__ */ I(xe, {
|
|
296
296
|
onSelect: le,
|
|
297
297
|
children: [/* @__PURE__ */ F(ae, {
|
|
298
298
|
className: "size-4",
|
|
299
299
|
"aria-hidden": "true"
|
|
300
300
|
}), n("brainyrich.reports.actions.downloadCsv", "Download CSV")]
|
|
301
|
-
}), /* @__PURE__ */ I(
|
|
301
|
+
}), /* @__PURE__ */ I(xe, {
|
|
302
302
|
onSelect: () => window.print(),
|
|
303
303
|
children: [/* @__PURE__ */ F(ue, {
|
|
304
304
|
className: "size-4",
|
|
@@ -424,7 +424,7 @@ function et({ households: e, translate: t }) {
|
|
|
424
424
|
children: [
|
|
425
425
|
/* @__PURE__ */ I(z, { children: [/* @__PURE__ */ I(H, {
|
|
426
426
|
className: "flex flex-row items-center justify-between gap-4",
|
|
427
|
-
children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F(U, { children: t("brainyrich.reports.scheduled.title", "Scheduled Client Reports") }), /* @__PURE__ */ F(V, { children: t("brainyrich.reports.scheduled.description", "Real recurring PDF report generation, regenerated on demand from a stored snapshot — no fabricated schedules.") })] }), /* @__PURE__ */ I(
|
|
427
|
+
children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F(U, { children: t("brainyrich.reports.scheduled.title", "Scheduled Client Reports") }), /* @__PURE__ */ F(V, { children: t("brainyrich.reports.scheduled.description", "Real recurring PDF report generation, regenerated on demand from a stored snapshot — no fabricated schedules.") })] }), /* @__PURE__ */ I(we, {
|
|
428
428
|
open: g,
|
|
429
429
|
onOpenChange: _,
|
|
430
430
|
children: [/* @__PURE__ */ I(R, {
|
|
@@ -434,8 +434,8 @@ function et({ households: e, translate: t }) {
|
|
|
434
434
|
className: "size-4",
|
|
435
435
|
"aria-hidden": "true"
|
|
436
436
|
}), t("brainyrich.reports.scheduled.new", "New schedule")]
|
|
437
|
-
}), /* @__PURE__ */ I(
|
|
438
|
-
/* @__PURE__ */ I(
|
|
437
|
+
}), /* @__PURE__ */ I(Ee, { children: [
|
|
438
|
+
/* @__PURE__ */ I(ke, { children: [/* @__PURE__ */ F(Ae, { children: t("brainyrich.reports.scheduled.newTitle", "New scheduled report") }), /* @__PURE__ */ F(De, { children: t("brainyrich.reports.scheduled.newDescription", "Recurring reports render the same real path as an on-demand generation.") })] }),
|
|
439
439
|
/* @__PURE__ */ I("div", {
|
|
440
440
|
className: "space-y-4",
|
|
441
441
|
children: [
|
|
@@ -444,7 +444,7 @@ function et({ households: e, translate: t }) {
|
|
|
444
444
|
children: [/* @__PURE__ */ F(W, {
|
|
445
445
|
htmlFor: "schedule-name",
|
|
446
446
|
children: t("brainyrich.reports.scheduled.fields.name", "Name")
|
|
447
|
-
}), /* @__PURE__ */ F(
|
|
447
|
+
}), /* @__PURE__ */ F(Ce, {
|
|
448
448
|
id: "schedule-name",
|
|
449
449
|
value: v,
|
|
450
450
|
onChange: (e) => y(e.target.value),
|
|
@@ -485,7 +485,7 @@ function et({ households: e, translate: t }) {
|
|
|
485
485
|
})
|
|
486
486
|
]
|
|
487
487
|
}),
|
|
488
|
-
/* @__PURE__ */ I(
|
|
488
|
+
/* @__PURE__ */ I(Oe, { children: [/* @__PURE__ */ F(Te, {
|
|
489
489
|
asChild: !0,
|
|
490
490
|
children: /* @__PURE__ */ F(R, {
|
|
491
491
|
variant: "secondary",
|
|
@@ -624,7 +624,7 @@ function tt({ branding: e, isSaving: t, onSave: n }) {
|
|
|
624
624
|
children: [/* @__PURE__ */ F(W, {
|
|
625
625
|
htmlFor: "branding-firm",
|
|
626
626
|
children: "Firm name"
|
|
627
|
-
}), /* @__PURE__ */ F(
|
|
627
|
+
}), /* @__PURE__ */ F(Ce, {
|
|
628
628
|
id: "branding-firm",
|
|
629
629
|
value: r,
|
|
630
630
|
onChange: (e) => i(e.target.value)
|
|
@@ -635,11 +635,11 @@ function tt({ branding: e, isSaving: t, onSave: n }) {
|
|
|
635
635
|
children: [/* @__PURE__ */ F(W, {
|
|
636
636
|
htmlFor: "branding-color",
|
|
637
637
|
children: "Primary color (hex)"
|
|
638
|
-
}), /* @__PURE__ */ F(
|
|
638
|
+
}), /* @__PURE__ */ F(Ce, {
|
|
639
639
|
id: "branding-color",
|
|
640
640
|
value: a,
|
|
641
641
|
onChange: (e) => o(e.target.value),
|
|
642
|
-
placeholder: "
|
|
642
|
+
placeholder: "Primary brand color"
|
|
643
643
|
})]
|
|
644
644
|
}),
|
|
645
645
|
/* @__PURE__ */ I("div", {
|
|
@@ -647,7 +647,7 @@ function tt({ branding: e, isSaving: t, onSave: n }) {
|
|
|
647
647
|
children: [/* @__PURE__ */ F(W, {
|
|
648
648
|
htmlFor: "branding-email",
|
|
649
649
|
children: "Contact email"
|
|
650
|
-
}), /* @__PURE__ */ F(
|
|
650
|
+
}), /* @__PURE__ */ F(Ce, {
|
|
651
651
|
id: "branding-email",
|
|
652
652
|
type: "email",
|
|
653
653
|
value: s,
|
|
@@ -1067,7 +1067,7 @@ function at({ householdsQuery: e, households: t, activeHousehold: n, onSelect: r
|
|
|
1067
1067
|
children: [/* @__PURE__ */ F("span", {
|
|
1068
1068
|
className: "text-sm text-text-secondary",
|
|
1069
1069
|
children: l("brainyrich.reports.statement.for", "Statement for")
|
|
1070
|
-
}), /* @__PURE__ */ I(
|
|
1070
|
+
}), /* @__PURE__ */ I(ye, { children: [/* @__PURE__ */ F(Se, {
|
|
1071
1071
|
asChild: !0,
|
|
1072
1072
|
children: /* @__PURE__ */ I(R, {
|
|
1073
1073
|
variant: "outline",
|
|
@@ -1084,10 +1084,10 @@ function at({ householdsQuery: e, households: t, activeHousehold: n, onSelect: r
|
|
|
1084
1084
|
})
|
|
1085
1085
|
]
|
|
1086
1086
|
})
|
|
1087
|
-
}), /* @__PURE__ */ F(
|
|
1087
|
+
}), /* @__PURE__ */ F(be, {
|
|
1088
1088
|
align: "start",
|
|
1089
1089
|
className: "max-h-72 overflow-y-auto",
|
|
1090
|
-
children: t.map((e) => /* @__PURE__ */ F(
|
|
1090
|
+
children: t.map((e) => /* @__PURE__ */ F(xe, {
|
|
1091
1091
|
onSelect: () => r(e.id),
|
|
1092
1092
|
children: e.name
|
|
1093
1093
|
}, e.id))
|