@burdenoff/microfe-brainyrich 2026.705.1 → 2026.706.1
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.js +13 -13
- package/dist/brainyrich/components/ActorName.js +38 -0
- package/dist/brainyrich/components/ActorName.js.map +1 -0
- package/dist/brainyrich/components/ConfirmDialog.js +25 -0
- package/dist/brainyrich/components/ConfirmDialog.js.map +1 -0
- package/dist/brainyrich/components/LoadMore.js +24 -0
- package/dist/brainyrich/components/LoadMore.js.map +1 -0
- package/dist/brainyrich/components/index.js +5 -0
- package/dist/brainyrich/components/notify.js +38 -0
- package/dist/brainyrich/components/notify.js.map +1 -0
- package/dist/brainyrich/components/pickers.js +232 -0
- package/dist/brainyrich/components/pickers.js.map +1 -0
- package/dist/brainyrich/components/reuse/CommentThread.js +208 -0
- package/dist/brainyrich/components/reuse/CommentThread.js.map +1 -0
- package/dist/brainyrich/components/reuse/DocumentsPanel.js +237 -0
- package/dist/brainyrich/components/reuse/DocumentsPanel.js.map +1 -0
- package/dist/brainyrich/components/reuse/TagField.js +206 -0
- package/dist/brainyrich/components/reuse/TagField.js.map +1 -0
- package/dist/brainyrich/components/reuse/transport.js +24 -0
- package/dist/brainyrich/components/reuse/transport.js.map +1 -0
- package/dist/brainyrich/hooks/useBrainyRichApi.js +644 -372
- package/dist/brainyrich/hooks/useBrainyRichApi.js.map +1 -1
- package/dist/brainyrich/pages/AccountsPage.js +346 -293
- package/dist/brainyrich/pages/AccountsPage.js.map +1 -1
- package/dist/brainyrich/pages/AiAssistantPage.js +339 -320
- package/dist/brainyrich/pages/AiAssistantPage.js.map +1 -1
- package/dist/brainyrich/pages/AnalyticsPage.js +178 -164
- package/dist/brainyrich/pages/AnalyticsPage.js.map +1 -1
- package/dist/brainyrich/pages/DashboardPage.js +195 -169
- package/dist/brainyrich/pages/DashboardPage.js.map +1 -1
- package/dist/brainyrich/pages/FamilyPage.js +247 -198
- package/dist/brainyrich/pages/FamilyPage.js.map +1 -1
- package/dist/brainyrich/pages/GoalsPage.js +478 -442
- package/dist/brainyrich/pages/GoalsPage.js.map +1 -1
- package/dist/brainyrich/pages/InvestmentsPage.js.map +1 -1
- package/dist/brainyrich/pages/PortfoliosPage.js +438 -396
- package/dist/brainyrich/pages/PortfoliosPage.js.map +1 -1
- package/dist/brainyrich/pages/ReportsPage.js +246 -213
- package/dist/brainyrich/pages/ReportsPage.js.map +1 -1
- package/dist/brainyrich/pages/RiskPage.js +321 -173
- package/dist/brainyrich/pages/RiskPage.js.map +1 -1
- package/dist/brainyrich/pages/TaxPage.js +258 -225
- package/dist/brainyrich/pages/TaxPage.js.map +1 -1
- package/dist/brainyrich/pages/_shared.js.map +1 -1
- package/dist/node_modules/sonner/dist/index.js +163 -0
- package/dist/node_modules/sonner/dist/index.js.map +1 -0
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaxPage.js","names":[],"sources":["../../../src/brainyrich/pages/TaxPage.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 DialogClose,\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 Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownRight,\n ArrowUpRight,\n Check,\n Coins,\n Landmark,\n MoreHorizontal,\n Receipt,\n ReceiptText,\n ShieldCheck,\n Sparkles,\n TrendingDown,\n TrendingUp,\n X,\n} from 'lucide-react';\nimport {\n useInsights,\n useTransactions,\n useAccounts,\n usePortfolios,\n useHoldings,\n useCreateInsight,\n useAcknowledgeInsight,\n useDismissInsight,\n useResolveInsight,\n} from '../hooks/useBrainyRichApi';\nimport type {\n BraAccount,\n BraAssetClass,\n BraHolding,\n BraInsight,\n BraInsightSeverity,\n} from '../types/bra';\nimport {\n SectionShell,\n KpiGrid,\n EmptyState,\n LoadingState,\n ErrorState,\n formatMoney,\n formatDate,\n statusBadgeVariant,\n deltaToneClass,\n} from './_shared';\n\n// ============================================================================\n// Domain helpers (derived from the SDL, never fabricated data)\n// ============================================================================\n\nconst SEVERITIES: BraInsightSeverity[] = ['INFO', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n\n/** Turn an UPPER_SNAKE enum member into a readable label. */\nfunction humanize(value: string): string {\n return value\n .toLowerCase()\n .split('_')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n/** Unrealized P/L for a holding = server market value − cost basis. */\nfunction unrealized(h: BraHolding): number {\n return h.marketValue - h.costBasis;\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\ninterface InsightFormState {\n title: string;\n detail: string;\n severity: BraInsightSeverity;\n /** Encoded as `${subjectType}:${id}` from the subject picker. */\n subject: string;\n}\n\nconst EMPTY_INSIGHT_FORM: InsightFormState = {\n title: '',\n detail: '',\n severity: 'MEDIUM',\n subject: '',\n};\n\nexport function TaxPage() {\n const {\n data: insights,\n isLoading: insightsLoading,\n isError: insightsError,\n refetch: refetchInsights,\n } = useInsights();\n\n const {\n data: transactions,\n isLoading: txLoading,\n isError: txError,\n refetch: refetchTransactions,\n } = useTransactions();\n\n const { data: accounts } = useAccounts();\n const { data: portfolios } = usePortfolios();\n\n const createInsight = useCreateInsight();\n const acknowledgeInsight = useAcknowledgeInsight();\n const dismissInsight = useDismissInsight();\n const resolveInsight = useResolveInsight();\n\n const [createOpen, setCreateOpen] = useState(false);\n const [form, setForm] = useState<InsightFormState>(EMPTY_INSIGHT_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n // ---- Tax-opportunity insights (client-filtered — the list hook has no type arg) ----\n const taxInsights = useMemo(\n () => insights.filter((i) => i.type === 'TAX_OPPORTUNITY'),\n [insights]\n );\n const openTax = taxInsights.filter((i) => i.status === 'OPEN');\n\n // ---- Realized activity partitioned from real transactions ----\n const sells = useMemo(() => transactions.filter((t) => t.type === 'SELL'), [transactions]);\n const income = useMemo(\n () => transactions.filter((t) => t.type === 'DIVIDEND' || t.type === 'INTEREST'),\n [transactions]\n );\n const realizedProceeds = sells.reduce((sum, t) => sum + t.amount, 0);\n const investmentIncome = income.reduce((sum, t) => sum + t.amount, 0);\n const moneyCurrency = sells[0]?.currency ?? income[0]?.currency ?? 'USD';\n\n const canCreate = accounts.length > 0 || portfolios.length > 0;\n\n function openCreate() {\n setForm(EMPTY_INSIGHT_FORM);\n setFormError(null);\n setCreateOpen(true);\n }\n\n function submitCreate() {\n const title = form.title.trim();\n if (!title) {\n setFormError('A title is required.');\n return;\n }\n if (!form.subject) {\n setFormError('Select the account or portfolio this opportunity applies to.');\n return;\n }\n const [subjectType, subjectId] = form.subject.split(':');\n if (!subjectType || !subjectId) {\n setFormError('Select a valid subject.');\n return;\n }\n createInsight.mutate(\n {\n type: 'TAX_OPPORTUNITY',\n severity: form.severity,\n title,\n detail: form.detail.trim() || null,\n subjectId,\n subjectType,\n },\n {\n onSuccess: () => {\n setCreateOpen(false);\n setForm(EMPTY_INSIGHT_FORM);\n },\n onError: (err: unknown) => {\n setFormError(err instanceof Error ? err.message : 'Could not create the tax insight.');\n },\n }\n );\n }\n\n const insightPending =\n acknowledgeInsight.isPending || dismissInsight.isPending || resolveInsight.isPending;\n\n return (\n <SectionShell\n title=\"Tax Center\"\n description=\"Act on tax-opportunity insights and review unrealized and realized gains — all from your live portfolio data.\"\n badges={[\n `${openTax.length} open opportunit${openTax.length === 1 ? 'y' : 'ies'}`,\n ]}\n actionLabel=\"New tax insight\"\n onAction={openCreate}\n >\n <KpiGrid\n items={[\n { label: 'Open tax opportunities', value: String(openTax.length) },\n { label: 'Tax insights (all)', value: String(taxInsights.length) },\n {\n label: 'Realized proceeds',\n value: formatMoney(realizedProceeds, moneyCurrency),\n trend: `${sells.length} sale${sells.length === 1 ? '' : 's'}`,\n },\n {\n label: 'Investment income',\n value: formatMoney(investmentIncome, moneyCurrency),\n trend: `${income.length} dividend/interest`,\n },\n ]}\n />\n\n <Tabs defaultValue=\"opportunities\" className=\"space-y-4\">\n <TabsList className=\"grid w-full grid-cols-3\">\n <TabsTrigger value=\"opportunities\">Tax Opportunities</TabsTrigger>\n <TabsTrigger value=\"unrealized\">Unrealized Gains</TabsTrigger>\n <TabsTrigger value=\"realized\">Realized Activity</TabsTrigger>\n </TabsList>\n\n {/* ---------------- Tax opportunities ---------------- */}\n <TabsContent value=\"opportunities\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Sparkles className=\"h-5 w-5\" aria-hidden=\"true\" />\n Tax-opportunity insights\n </CardTitle>\n <CardDescription>\n Tax-loss harvesting and other tax signals detected across your accounts and\n portfolios.\n </CardDescription>\n </CardHeader>\n <CardContent>\n {insightsLoading ? (\n <LoadingState />\n ) : insightsError ? (\n <ErrorState onRetry={() => refetchInsights()} />\n ) : taxInsights.length === 0 ? (\n <EmptyState\n icon={<Receipt className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No tax opportunities yet\"\n description=\"Tax-opportunity insights appear here as the backend generates them from your holdings. You can also flag one manually.\"\n action={\n canCreate ? (\n <Button size=\"sm\" onClick={openCreate}>\n <Sparkles className=\"size-4\" aria-hidden=\"true\" />\n New tax insight\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {taxInsights.map((insight) => (\n <TaxInsightRow\n key={insight.id}\n insight={insight}\n actionPending={insightPending}\n onAcknowledge={() => acknowledgeInsight.mutate(insight.id)}\n onResolve={() => resolveInsight.mutate(insight.id)}\n onDismiss={() => dismissInsight.mutate(insight.id)}\n />\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* ---------------- Unrealized gains ---------------- */}\n <TabsContent value=\"unrealized\">\n <UnrealizedGainsTab accounts={accounts} />\n </TabsContent>\n\n {/* ---------------- Realized activity ---------------- */}\n <TabsContent value=\"realized\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <ReceiptText className=\"h-5 w-5\" aria-hidden=\"true\" />\n Realized activity\n </CardTitle>\n <CardDescription>\n Realized sales, dividends and interest — the taxable events recorded on your\n accounts.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n {txLoading ? (\n <LoadingState />\n ) : txError ? (\n <ErrorState onRetry={() => refetchTransactions()} />\n ) : sells.length === 0 && income.length === 0 ? (\n <EmptyState\n icon={<ReceiptText className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No taxable activity recorded\"\n description=\"Sales, dividends and interest show up here once transactions are recorded against your accounts.\"\n />\n ) : (\n <>\n {/* Realized sales */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Landmark className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">Realized sales</h3>\n <Badge variant=\"secondary\">{sells.length}</Badge>\n </div>\n {sells.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No sales recorded.</p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Quantity</TableHead>\n <TableHead className=\"text-right\">Price</TableHead>\n <TableHead className=\"text-right\">Proceeds</TableHead>\n <TableHead>Trade date</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sells.map((t) => (\n <TableRow key={t.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {t.symbol ?? '—'}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {t.quantity}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {formatMoney(t.price, t.currency)}\n </TableCell>\n <TableCell className=\"text-right font-medium text-text-primary\">\n {formatMoney(t.amount, t.currency)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(t.tradeDate)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n\n {/* Investment income */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Coins className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Dividends & interest\n </h3>\n <Badge variant=\"secondary\">{income.length}</Badge>\n </div>\n {income.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No income recorded.</p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Type</TableHead>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Amount</TableHead>\n <TableHead>Trade date</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {income.map((t) => (\n <TableRow key={t.id}>\n <TableCell>\n <Badge variant=\"outline\">{humanize(t.type)}</Badge>\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {t.symbol ?? '—'}\n </TableCell>\n <TableCell className=\"text-right font-medium text-status-success-text\">\n {formatMoney(t.amount, t.currency)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(t.tradeDate)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n </>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n\n {/* ---------------- Create tax insight dialog ---------------- */}\n <Dialog\n open={createOpen}\n onOpenChange={(open) => {\n setCreateOpen(open);\n if (!open) setFormError(null);\n }}\n >\n <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>New tax insight</DialogTitle>\n <DialogDescription>\n Flag a tax opportunity against one of your accounts or portfolios.\n </DialogDescription>\n </DialogHeader>\n\n {!canCreate ? (\n <EmptyState\n title=\"No subject available\"\n description=\"Create an account or portfolio first, then flag a tax opportunity against it.\"\n />\n ) : (\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-title\">Title</Label>\n <Input\n id=\"tax-title\"\n value={form.title}\n onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}\n placeholder=\"e.g. Harvest losses in tech holdings\"\n />\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-severity\">Severity</Label>\n <Select\n value={form.severity}\n onValueChange={(v) => setForm((f) => ({ ...f, severity: v as BraInsightSeverity }))}\n >\n <SelectTrigger id=\"tax-severity\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SEVERITIES.map((s) => (\n <SelectItem key={s} value={s}>\n {humanize(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-subject\">Applies to</Label>\n <Select\n value={form.subject}\n onValueChange={(v) => setForm((f) => ({ ...f, subject: v }))}\n >\n <SelectTrigger id=\"tax-subject\">\n <SelectValue placeholder=\"Select subject\" />\n </SelectTrigger>\n <SelectContent>\n {portfolios.map((p) => (\n <SelectItem key={`portfolio:${p.id}`} value={`portfolio:${p.id}`}>\n Portfolio · {p.name}\n </SelectItem>\n ))}\n {accounts.map((a) => (\n <SelectItem key={`account:${a.id}`} value={`account:${a.id}`}>\n Account · {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-detail\">Detail</Label>\n <Textarea\n id=\"tax-detail\"\n value={form.detail}\n onChange={(e) => setForm((f) => ({ ...f, detail: e.target.value }))}\n rows={3}\n placeholder=\"What is the opportunity and how should it be actioned?\"\n />\n </div>\n\n {formError ? (\n <p className=\"text-sm text-status-error-text\" role=\"alert\">\n {formError}\n </p>\n ) : null}\n </div>\n )}\n\n <DialogFooter>\n <DialogClose asChild>\n <Button variant=\"outline\" disabled={createInsight.isPending}>\n Cancel\n </Button>\n </DialogClose>\n <Button\n onClick={submitCreate}\n disabled={!canCreate || !form.title.trim() || !form.subject || createInsight.isPending}\n >\n {createInsight.isPending ? 'Creating…' : 'Create insight'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </SectionShell>\n );\n}\n\n// ============================================================================\n// Unrealized gains tab — per-account holdings P/L (real market value − cost basis)\n// ============================================================================\n\nfunction UnrealizedGainsTab({ accounts }: { accounts: BraAccount[] }) {\n const [accountId, setAccountId] = useState<string>('');\n const selectedId = accountId || accounts[0]?.id;\n const selectedAccount = accounts.find((a) => a.id === selectedId);\n\n const {\n data: holdings,\n isLoading,\n isError,\n refetch,\n } = useHoldings({ accountId: selectedId });\n\n const currency = selectedAccount?.currency ?? holdings[0]?.currency ?? 'USD';\n\n const totalUnrealized = holdings.reduce((sum, h) => sum + unrealized(h), 0);\n const gainers = holdings.filter((h) => unrealized(h) > 0).length;\n const losers = holdings.filter((h) => unrealized(h) < 0).length;\n\n // Group holdings by asset class with subtotals (grouped, colored by gain/loss).\n const groups = useMemo(() => {\n const map = new Map<BraAssetClass, BraHolding[]>();\n for (const h of holdings) {\n const arr = map.get(h.assetClass) ?? [];\n arr.push(h);\n map.set(h.assetClass, arr);\n }\n return Array.from(map.entries());\n }, [holdings]);\n\n if (accounts.length === 0) {\n return (\n <Card>\n <CardContent className=\"pt-6\">\n <EmptyState\n icon={<TrendingUp className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No accounts yet\"\n description=\"Add an account with holdings to see unrealized gains and losses.\"\n />\n </CardContent>\n </Card>\n );\n }\n\n return (\n <Card>\n <CardHeader className=\"flex flex-col gap-4 md:flex-row md:items-start md:justify-between\">\n <div>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-5 w-5\" aria-hidden=\"true\" />\n Unrealized gains & losses\n </CardTitle>\n <CardDescription>\n Market value minus cost basis for each holding in the selected account.\n </CardDescription>\n </div>\n <div className=\"w-full md:w-64\">\n <Label htmlFor=\"unrealized-account\" className=\"sr-only\">\n Account\n </Label>\n <Select value={selectedId ?? ''} onValueChange={setAccountId}>\n <SelectTrigger id=\"unrealized-account\">\n <SelectValue placeholder=\"Select account\" />\n </SelectTrigger>\n <SelectContent>\n {accounts.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLoading ? (\n <LoadingState />\n ) : isError ? (\n <ErrorState onRetry={() => refetch()} />\n ) : holdings.length === 0 ? (\n <EmptyState\n icon={<TrendingUp className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No holdings in this account\"\n description=\"Unrealized gains appear once this account has holdings with a cost basis.\"\n />\n ) : (\n <>\n <KpiGrid\n items={[\n {\n label: 'Unrealized P/L',\n value: formatMoney(totalUnrealized, currency),\n trend: totalUnrealized >= 0 ? 'Net gain' : 'Net loss',\n deltaTone: totalUnrealized >= 0 ? 'positive' : 'negative',\n },\n { label: 'Holdings', value: String(holdings.length) },\n { label: 'Gainers', value: String(gainers) },\n { label: 'Losers', value: String(losers) },\n ]}\n />\n\n <div className=\"space-y-6\">\n {groups.map(([assetClass, rows]) => {\n const groupTotal = rows.reduce((sum, h) => sum + unrealized(h), 0);\n return (\n <div key={assetClass} className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {humanize(assetClass)}\n </h3>\n <Badge variant=\"secondary\">{rows.length}</Badge>\n </div>\n <GainLoss value={groupTotal} currency={currency} />\n </div>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Quantity</TableHead>\n <TableHead className=\"text-right\">Cost basis</TableHead>\n <TableHead className=\"text-right\">Market value</TableHead>\n <TableHead className=\"text-right\">Unrealized</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((h) => (\n <TableRow key={h.id}>\n <TableCell>\n <p className=\"font-medium text-text-primary\">{h.symbol}</p>\n {h.name ? (\n <p className=\"text-xs text-text-secondary\">{h.name}</p>\n ) : null}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {h.quantity}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {formatMoney(h.costBasis, h.currency)}\n </TableCell>\n <TableCell className=\"text-right text-text-primary\">\n {formatMoney(h.marketValue, h.currency)}\n </TableCell>\n <TableCell className=\"text-right\">\n <GainLoss value={unrealized(h)} currency={h.currency} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </div>\n );\n })}\n </div>\n </>\n )}\n </CardContent>\n </Card>\n );\n}\n\n/** Signed money with an arrow icon + semantic color (color is never the only signal). */\nfunction GainLoss({ value, currency }: { value: number; currency: string }) {\n const positive = value > 0;\n const negative = value < 0;\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : TrendingDown;\n const sign = positive ? '+' : '';\n return (\n <span\n className={`inline-flex items-center justify-end gap-1 font-semibold ${deltaToneClass(value)}`}\n >\n {value !== 0 ? <Icon className=\"size-4\" aria-hidden=\"true\" /> : null}\n {sign}\n {formatMoney(value, currency)}\n </span>\n );\n}\n\n// ============================================================================\n// Tax-insight row (acknowledge / resolve / dismiss)\n// ============================================================================\n\nfunction TaxInsightRow({\n insight,\n actionPending,\n onAcknowledge,\n onResolve,\n onDismiss,\n}: {\n insight: BraInsight;\n actionPending: boolean;\n onAcknowledge: () => void;\n onResolve: () => void;\n onDismiss: () => void;\n}) {\n const terminal = insight.status === 'RESOLVED' || insight.status === 'DISMISSED';\n return (\n <div className=\"flex items-start justify-between gap-4 rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-medium text-text-primary\">{insight.title}</p>\n <Badge variant={statusBadgeVariant(insight.severity)}>{humanize(insight.severity)}</Badge>\n <Badge variant={statusBadgeVariant(insight.status)}>{humanize(insight.status)}</Badge>\n </div>\n {insight.detail ? (\n <p className=\"text-sm text-text-secondary\">{insight.detail}</p>\n ) : null}\n <p className=\"text-xs text-text-muted\">\n {humanize(insight.subjectType)} · Detected {formatDate(insight.createdAt)}\n </p>\n </div>\n {terminal ? (\n <span className=\"mt-1 text-xs text-text-muted\">Closed</span>\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Actions for ${insight.title}`}\n disabled={actionPending}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>Actions</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {insight.status === 'OPEN' ? (\n <DropdownMenuItem onClick={onAcknowledge}>\n <Check className=\"size-4\" aria-hidden=\"true\" />\n Acknowledge\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem onClick={onResolve}>\n <ShieldCheck className=\"size-4\" aria-hidden=\"true\" />\n Resolve\n </DropdownMenuItem>\n <DropdownMenuItem className=\"text-status-error-text\" onClick={onDismiss}>\n <X className=\"size-4\" aria-hidden=\"true\" />\n Dismiss\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaxPage.js","names":[],"sources":["../../../src/brainyrich/pages/TaxPage.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 DialogClose,\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 Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n ArrowDownRight,\n ArrowUpRight,\n Check,\n Coins,\n Landmark,\n MoreHorizontal,\n Receipt,\n ReceiptText,\n ShieldCheck,\n Sparkles,\n TrendingDown,\n TrendingUp,\n X,\n} from 'lucide-react';\nimport {\n usePagedInsights,\n useAllTransactions,\n useAllAccounts,\n usePortfolios,\n useHoldings,\n useCreateInsight,\n useAcknowledgeInsight,\n useDismissInsight,\n useResolveInsight,\n} from '../hooks/useBrainyRichApi';\nimport { LoadMore, ConfirmDialog } from '../components';\nimport type {\n BraAccount,\n BraAssetClass,\n BraHolding,\n BraInsight,\n BraInsightSeverity,\n} from '../types/bra';\nimport {\n SectionShell,\n KpiGrid,\n EmptyState,\n LoadingState,\n ErrorState,\n formatMoney,\n formatDate,\n statusBadgeVariant,\n deltaToneClass,\n} from './_shared';\n\n// ============================================================================\n// Domain helpers (derived from the SDL, never fabricated data)\n// ============================================================================\n\nconst SEVERITIES: BraInsightSeverity[] = ['INFO', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n\n/** Turn an UPPER_SNAKE enum member into a readable label. */\nfunction humanize(value: string): string {\n return value\n .toLowerCase()\n .split('_')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n/** Unrealized P/L for a holding = server market value − cost basis. */\nfunction unrealized(h: BraHolding): number {\n return h.marketValue - h.costBasis;\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\ninterface InsightFormState {\n title: string;\n detail: string;\n severity: BraInsightSeverity;\n /** Encoded as `${subjectType}:${id}` from the subject picker. */\n subject: string;\n}\n\nconst EMPTY_INSIGHT_FORM: InsightFormState = {\n title: '',\n detail: '',\n severity: 'MEDIUM',\n subject: '',\n};\n\nexport function TaxPage() {\n // Insights are a *Connection — paginate with LoadMore. Tax-opportunity\n // insights are client-filtered (the list query has no `type` argument).\n const {\n items: insights,\n hasMore: insightsHasMore,\n loadMore: loadMoreInsights,\n isLoading: insightsLoading,\n isFetchingNextPage: insightsFetchingMore,\n isError: insightsError,\n refetch: refetchInsights,\n } = usePagedInsights();\n\n // Realized activity + income aggregated over the FULL transaction set\n // (not a truncated first page) via the fetch-all helper.\n const {\n data: transactions,\n isLoading: txLoading,\n isError: txError,\n refetch: refetchTransactions,\n } = useAllTransactions();\n\n // ALL accounts (with holdings) — drives the portfolio-wide unrealized total.\n const { data: accounts, isLoading: accountsLoading } = useAllAccounts();\n const { data: portfolios } = usePortfolios();\n\n const createInsight = useCreateInsight();\n const acknowledgeInsight = useAcknowledgeInsight();\n const dismissInsight = useDismissInsight();\n const resolveInsight = useResolveInsight();\n\n const [createOpen, setCreateOpen] = useState(false);\n const [form, setForm] = useState<InsightFormState>(EMPTY_INSIGHT_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n const [dismissTarget, setDismissTarget] = useState<BraInsight | null>(null);\n\n // ---- Tax-opportunity insights (client-filtered — the list hook has no type arg) ----\n const taxInsights = useMemo(\n () => insights.filter((i) => i.type === 'TAX_OPPORTUNITY'),\n [insights]\n );\n const openTax = taxInsights.filter((i) => i.status === 'OPEN');\n\n // ---- Realized activity partitioned from real transactions ----\n const sells = useMemo(() => transactions.filter((t) => t.type === 'SELL'), [transactions]);\n const income = useMemo(\n () => transactions.filter((t) => t.type === 'DIVIDEND' || t.type === 'INTEREST'),\n [transactions]\n );\n const realizedProceeds = sells.reduce((sum, t) => sum + t.amount, 0);\n const investmentIncome = income.reduce((sum, t) => sum + t.amount, 0);\n const moneyCurrency = sells[0]?.currency ?? income[0]?.currency ?? 'USD';\n\n // ---- Portfolio-wide unrealized P/L across EVERY account's holdings ----\n const totalUnrealizedAll = useMemo(\n () =>\n accounts.reduce(\n (sum, a) => sum + a.holdings.reduce((s, h) => s + unrealized(h), 0),\n 0\n ),\n [accounts]\n );\n const unrealizedCurrency = accounts[0]?.currency ?? moneyCurrency;\n\n const canCreate = accounts.length > 0 || portfolios.length > 0;\n\n function openCreate() {\n setForm(EMPTY_INSIGHT_FORM);\n setFormError(null);\n setCreateOpen(true);\n }\n\n function submitCreate() {\n const title = form.title.trim();\n if (!title) {\n setFormError('A title is required.');\n return;\n }\n if (!form.subject) {\n setFormError('Select the account or portfolio this opportunity applies to.');\n return;\n }\n const [subjectType, subjectId] = form.subject.split(':');\n if (!subjectType || !subjectId) {\n setFormError('Select a valid subject.');\n return;\n }\n createInsight.mutate(\n {\n type: 'TAX_OPPORTUNITY',\n severity: form.severity,\n title,\n detail: form.detail.trim() || null,\n subjectId,\n subjectType,\n },\n {\n onSuccess: () => {\n setCreateOpen(false);\n setForm(EMPTY_INSIGHT_FORM);\n },\n onError: (err: unknown) => {\n setFormError(err instanceof Error ? err.message : 'Could not create the tax insight.');\n },\n }\n );\n }\n\n const insightPending =\n acknowledgeInsight.isPending || dismissInsight.isPending || resolveInsight.isPending;\n\n return (\n <SectionShell\n title=\"Tax Center\"\n description=\"Act on tax-opportunity insights and review unrealized and realized gains — all from your live portfolio data.\"\n badges={[`${openTax.length} open opportunit${openTax.length === 1 ? 'y' : 'ies'}`]}\n actionLabel=\"New tax insight\"\n onAction={openCreate}\n >\n <KpiGrid\n items={[\n { label: 'Open tax opportunities', value: String(openTax.length) },\n { label: 'Tax insights (all)', value: String(taxInsights.length) },\n {\n label: 'Realized proceeds',\n value: formatMoney(realizedProceeds, moneyCurrency),\n trend: `${sells.length} sale${sells.length === 1 ? '' : 's'}`,\n },\n {\n label: 'Investment income',\n value: formatMoney(investmentIncome, moneyCurrency),\n trend: `${income.length} dividend/interest`,\n },\n {\n label: 'Unrealized P/L (all accounts)',\n value: accountsLoading ? '—' : formatMoney(totalUnrealizedAll, unrealizedCurrency),\n trend: accountsLoading\n ? 'Loading…'\n : totalUnrealizedAll >= 0\n ? 'Net gain'\n : 'Net loss',\n deltaTone: totalUnrealizedAll >= 0 ? 'positive' : 'negative',\n },\n ]}\n />\n\n <Tabs defaultValue=\"opportunities\" className=\"space-y-4\">\n <TabsList className=\"grid w-full grid-cols-3\">\n <TabsTrigger value=\"opportunities\">Tax Opportunities</TabsTrigger>\n <TabsTrigger value=\"unrealized\">Unrealized Gains</TabsTrigger>\n <TabsTrigger value=\"realized\">Realized Activity</TabsTrigger>\n </TabsList>\n\n {/* ---------------- Tax opportunities ---------------- */}\n <TabsContent value=\"opportunities\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Sparkles className=\"h-5 w-5\" aria-hidden=\"true\" />\n Tax-opportunity insights\n </CardTitle>\n <CardDescription>\n Tax-loss harvesting and other tax signals detected across your accounts and\n portfolios.\n </CardDescription>\n </CardHeader>\n <CardContent>\n {insightsLoading ? (\n <LoadingState />\n ) : insightsError ? (\n <ErrorState onRetry={() => refetchInsights()} />\n ) : taxInsights.length === 0 ? (\n <EmptyState\n icon={<Receipt className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No tax opportunities yet\"\n description=\"Tax-opportunity insights appear here as the backend generates them from your holdings. You can also flag one manually.\"\n action={\n canCreate ? (\n <Button size=\"sm\" onClick={openCreate}>\n <Sparkles className=\"size-4\" aria-hidden=\"true\" />\n New tax insight\n </Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {taxInsights.map((insight) => (\n <TaxInsightRow\n key={insight.id}\n insight={insight}\n actionPending={insightPending}\n onAcknowledge={() => acknowledgeInsight.mutate(insight.id)}\n onResolve={() => resolveInsight.mutate(insight.id)}\n onDismiss={() => setDismissTarget(insight)}\n />\n ))}\n <LoadMore\n hasMore={insightsHasMore}\n onClick={loadMoreInsights}\n loading={insightsFetchingMore}\n label=\"Load more insights\"\n />\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* ---------------- Unrealized gains ---------------- */}\n <TabsContent value=\"unrealized\">\n <UnrealizedGainsTab accounts={accounts} />\n </TabsContent>\n\n {/* ---------------- Realized activity ---------------- */}\n <TabsContent value=\"realized\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <ReceiptText className=\"h-5 w-5\" aria-hidden=\"true\" />\n Realized activity\n </CardTitle>\n <CardDescription>\n Realized sales, dividends and interest — the taxable events recorded on your\n accounts.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-6\">\n {txLoading ? (\n <LoadingState />\n ) : txError ? (\n <ErrorState onRetry={() => refetchTransactions()} />\n ) : sells.length === 0 && income.length === 0 ? (\n <EmptyState\n icon={<ReceiptText className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No taxable activity recorded\"\n description=\"Sales, dividends and interest show up here once transactions are recorded against your accounts.\"\n />\n ) : (\n <>\n {/* Realized sales */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Landmark className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">Realized sales</h3>\n <Badge variant=\"secondary\">{sells.length}</Badge>\n </div>\n {sells.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No sales recorded.</p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Quantity</TableHead>\n <TableHead className=\"text-right\">Price</TableHead>\n <TableHead className=\"text-right\">Proceeds</TableHead>\n <TableHead>Trade date</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sells.map((t) => (\n <TableRow key={t.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {t.symbol ?? '—'}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {t.quantity}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {formatMoney(t.price, t.currency)}\n </TableCell>\n <TableCell className=\"text-right font-medium text-text-primary\">\n {formatMoney(t.amount, t.currency)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(t.tradeDate)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n\n {/* Investment income */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Coins className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Dividends & interest\n </h3>\n <Badge variant=\"secondary\">{income.length}</Badge>\n </div>\n {income.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No income recorded.</p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Type</TableHead>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Amount</TableHead>\n <TableHead>Trade date</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {income.map((t) => (\n <TableRow key={t.id}>\n <TableCell>\n <Badge variant=\"outline\">{humanize(t.type)}</Badge>\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {t.symbol ?? '—'}\n </TableCell>\n <TableCell className=\"text-right font-medium text-status-success-text\">\n {formatMoney(t.amount, t.currency)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(t.tradeDate)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n </>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n\n {/* ---------------- Create tax insight dialog ---------------- */}\n <Dialog\n open={createOpen}\n onOpenChange={(open) => {\n setCreateOpen(open);\n if (!open) setFormError(null);\n }}\n >\n <DialogContent className=\"max-h-[90vh] overflow-y-auto sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>New tax insight</DialogTitle>\n <DialogDescription>\n Flag a tax opportunity against one of your accounts or portfolios.\n </DialogDescription>\n </DialogHeader>\n\n {!canCreate ? (\n <EmptyState\n title=\"No subject available\"\n description=\"Create an account or portfolio first, then flag a tax opportunity against it.\"\n />\n ) : (\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-title\">Title</Label>\n <Input\n id=\"tax-title\"\n value={form.title}\n onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}\n placeholder=\"e.g. Harvest losses in tech holdings\"\n />\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-severity\">Severity</Label>\n <Select\n value={form.severity}\n onValueChange={(v) =>\n setForm((f) => ({ ...f, severity: v as BraInsightSeverity }))\n }\n >\n <SelectTrigger id=\"tax-severity\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SEVERITIES.map((s) => (\n <SelectItem key={s} value={s}>\n {humanize(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-subject\">Applies to</Label>\n <Select\n value={form.subject}\n onValueChange={(v) => setForm((f) => ({ ...f, subject: v }))}\n >\n <SelectTrigger id=\"tax-subject\">\n <SelectValue placeholder=\"Select subject\" />\n </SelectTrigger>\n <SelectContent>\n {portfolios.map((p) => (\n <SelectItem key={`portfolio:${p.id}`} value={`portfolio:${p.id}`}>\n Portfolio · {p.name}\n </SelectItem>\n ))}\n {accounts.map((a) => (\n <SelectItem key={`account:${a.id}`} value={`account:${a.id}`}>\n Account · {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"tax-detail\">Detail</Label>\n <Textarea\n id=\"tax-detail\"\n value={form.detail}\n onChange={(e) => setForm((f) => ({ ...f, detail: e.target.value }))}\n rows={3}\n placeholder=\"What is the opportunity and how should it be actioned?\"\n />\n </div>\n\n {formError ? (\n <p className=\"text-sm text-status-error-text\" role=\"alert\">\n {formError}\n </p>\n ) : null}\n </div>\n )}\n\n <DialogFooter>\n <DialogClose asChild>\n <Button variant=\"outline\" disabled={createInsight.isPending}>\n Cancel\n </Button>\n </DialogClose>\n <Button\n onClick={submitCreate}\n disabled={\n !canCreate || !form.title.trim() || !form.subject || createInsight.isPending\n }\n >\n {createInsight.isPending ? 'Creating…' : 'Create insight'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {/* ---------------- Dismiss confirmation ---------------- */}\n <ConfirmDialog\n open={Boolean(dismissTarget)}\n onOpenChange={(open) => {\n if (!open) setDismissTarget(null);\n }}\n title=\"Dismiss tax insight?\"\n description={\n dismissTarget ? (\n <>\n “{dismissTarget.title}” will be marked as dismissed and removed from your\n open opportunities. This does not delete the underlying holdings or transactions.\n </>\n ) : undefined\n }\n confirmLabel=\"Dismiss insight\"\n pendingLabel=\"Dismissing…\"\n destructive\n pending={dismissInsight.isPending}\n onConfirm={() => {\n if (!dismissTarget) return;\n dismissInsight.mutate(dismissTarget.id, {\n onSuccess: () => setDismissTarget(null),\n });\n }}\n />\n </SectionShell>\n );\n}\n\n// ============================================================================\n// Unrealized gains tab — per-account holdings P/L (real market value − cost basis)\n// ============================================================================\n\nfunction UnrealizedGainsTab({ accounts }: { accounts: BraAccount[] }) {\n const [accountId, setAccountId] = useState<string>('');\n const selectedId = accountId || accounts[0]?.id;\n const selectedAccount = accounts.find((a) => a.id === selectedId);\n\n const { data: holdings, isLoading, isError, refetch } = useHoldings({ accountId: selectedId });\n\n const currency = selectedAccount?.currency ?? holdings[0]?.currency ?? 'USD';\n\n const totalUnrealized = holdings.reduce((sum, h) => sum + unrealized(h), 0);\n const gainers = holdings.filter((h) => unrealized(h) > 0).length;\n const losers = holdings.filter((h) => unrealized(h) < 0).length;\n\n // Group holdings by asset class with subtotals (grouped, colored by gain/loss).\n const groups = useMemo(() => {\n const map = new Map<BraAssetClass, BraHolding[]>();\n for (const h of holdings) {\n const arr = map.get(h.assetClass) ?? [];\n arr.push(h);\n map.set(h.assetClass, arr);\n }\n return Array.from(map.entries());\n }, [holdings]);\n\n if (accounts.length === 0) {\n return (\n <Card>\n <CardContent className=\"pt-6\">\n <EmptyState\n icon={<TrendingUp className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No accounts yet\"\n description=\"Add an account with holdings to see unrealized gains and losses.\"\n />\n </CardContent>\n </Card>\n );\n }\n\n return (\n <Card>\n <CardHeader className=\"flex flex-col gap-4 md:flex-row md:items-start md:justify-between\">\n <div>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-5 w-5\" aria-hidden=\"true\" />\n Unrealized gains & losses\n </CardTitle>\n <CardDescription>\n Market value minus cost basis for each holding in the selected account.\n </CardDescription>\n </div>\n <div className=\"w-full md:w-64\">\n <Label htmlFor=\"unrealized-account\" className=\"sr-only\">\n Account\n </Label>\n <Select value={selectedId ?? ''} onValueChange={setAccountId}>\n <SelectTrigger id=\"unrealized-account\">\n <SelectValue placeholder=\"Select account\" />\n </SelectTrigger>\n <SelectContent>\n {accounts.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLoading ? (\n <LoadingState />\n ) : isError ? (\n <ErrorState onRetry={() => refetch()} />\n ) : holdings.length === 0 ? (\n <EmptyState\n icon={<TrendingUp className=\"size-6\" aria-hidden=\"true\" />}\n title=\"No holdings in this account\"\n description=\"Unrealized gains appear once this account has holdings with a cost basis.\"\n />\n ) : (\n <>\n <KpiGrid\n items={[\n {\n label: 'Unrealized P/L',\n value: formatMoney(totalUnrealized, currency),\n trend: totalUnrealized >= 0 ? 'Net gain' : 'Net loss',\n deltaTone: totalUnrealized >= 0 ? 'positive' : 'negative',\n },\n { label: 'Holdings', value: String(holdings.length) },\n { label: 'Gainers', value: String(gainers) },\n { label: 'Losers', value: String(losers) },\n ]}\n />\n\n <div className=\"space-y-6\">\n {groups.map(([assetClass, rows]) => {\n const groupTotal = rows.reduce((sum, h) => sum + unrealized(h), 0);\n return (\n <div key={assetClass} className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {humanize(assetClass)}\n </h3>\n <Badge variant=\"secondary\">{rows.length}</Badge>\n </div>\n <GainLoss value={groupTotal} currency={currency} />\n </div>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Symbol</TableHead>\n <TableHead className=\"text-right\">Quantity</TableHead>\n <TableHead className=\"text-right\">Cost basis</TableHead>\n <TableHead className=\"text-right\">Market value</TableHead>\n <TableHead className=\"text-right\">Unrealized</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((h) => (\n <TableRow key={h.id}>\n <TableCell>\n <p className=\"font-medium text-text-primary\">{h.symbol}</p>\n {h.name ? (\n <p className=\"text-xs text-text-secondary\">{h.name}</p>\n ) : null}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {h.quantity}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {formatMoney(h.costBasis, h.currency)}\n </TableCell>\n <TableCell className=\"text-right text-text-primary\">\n {formatMoney(h.marketValue, h.currency)}\n </TableCell>\n <TableCell className=\"text-right\">\n <GainLoss value={unrealized(h)} currency={h.currency} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </div>\n );\n })}\n </div>\n </>\n )}\n </CardContent>\n </Card>\n );\n}\n\n/** Signed money with an arrow icon + semantic color (color is never the only signal). */\nfunction GainLoss({ value, currency }: { value: number; currency: string }) {\n const positive = value > 0;\n const negative = value < 0;\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : TrendingDown;\n const sign = positive ? '+' : '';\n return (\n <span\n className={`inline-flex items-center justify-end gap-1 font-semibold ${deltaToneClass(value)}`}\n >\n {value !== 0 ? <Icon className=\"size-4\" aria-hidden=\"true\" /> : null}\n {sign}\n {formatMoney(value, currency)}\n </span>\n );\n}\n\n// ============================================================================\n// Tax-insight row (acknowledge / resolve / dismiss)\n// ============================================================================\n\nfunction TaxInsightRow({\n insight,\n actionPending,\n onAcknowledge,\n onResolve,\n onDismiss,\n}: {\n insight: BraInsight;\n actionPending: boolean;\n onAcknowledge: () => void;\n onResolve: () => void;\n onDismiss: () => void;\n}) {\n const terminal = insight.status === 'RESOLVED' || insight.status === 'DISMISSED';\n return (\n <div className=\"flex items-start justify-between gap-4 rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-medium text-text-primary\">{insight.title}</p>\n <Badge variant={statusBadgeVariant(insight.severity)}>{humanize(insight.severity)}</Badge>\n <Badge variant={statusBadgeVariant(insight.status)}>{humanize(insight.status)}</Badge>\n </div>\n {insight.detail ? <p className=\"text-sm text-text-secondary\">{insight.detail}</p> : null}\n <p className=\"text-xs text-text-muted\">\n {humanize(insight.subjectType)} · Detected {formatDate(insight.createdAt)}\n </p>\n </div>\n {terminal ? (\n <span className=\"mt-1 text-xs text-text-muted\">Closed</span>\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Actions for ${insight.title}`}\n disabled={actionPending}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>Actions</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {insight.status === 'OPEN' ? (\n <DropdownMenuItem onClick={onAcknowledge}>\n <Check className=\"size-4\" aria-hidden=\"true\" />\n Acknowledge\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem onClick={onResolve}>\n <ShieldCheck className=\"size-4\" aria-hidden=\"true\" />\n Resolve\n </DropdownMenuItem>\n <DropdownMenuItem className=\"text-status-error-text\" onClick={onDismiss}>\n <X className=\"size-4\" aria-hidden=\"true\" />\n Dismiss\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_shared.js","names":[],"sources":["../../../src/brainyrich/pages/_shared.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport {\n ArrowDownRight,\n ArrowUpRight,\n Inbox,\n Minus,\n RefreshCw,\n TriangleAlert,\n} from 'lucide-react';\nimport { Badge, Button, Card, CardContent, Skeleton } from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Formatters\n// ============================================================================\n\n/** USD currency, no cents. Pass a currency code to override. */\nexport function formatMoney(value: number, currency = 'USD'): string {\n if (!Number.isFinite(value)) return '—';\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/** Compact money/number, e.g. 1.2M, 950K. */\nexport function formatCompact(value: number, currency?: string): string {\n if (!Number.isFinite(value)) return '—';\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n ...(currency ? { style: 'currency', currency } : {}),\n }).format(value);\n}\n\n/** Percentage from a whole-number percent (e.g. 12.5 → \"12.5%\"). */\nexport function formatPct(value: number | null | undefined, fractionDigits = 1): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '—';\n return `${value.toFixed(fractionDigits)}%`;\n}\n\n/**\n * Guarded date formatter. Only wraps a present ISO string in `new Date()` at\n * render time — never calls `.getTime()` on a raw scalar and never `new Date(0)`.\n * Returns an em-dash for absent/invalid values.\n */\nexport function formatDate(iso?: string | null): string {\n if (!iso) return '—';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n }).format(d);\n}\n\n// ============================================================================\n// Status → badge variant (semantic, no raw colors)\n// ============================================================================\n\n/**\n * Map a domain status/severity string to a shadcn Badge variant. Accepts the\n * Bra* enum members (ACTIVE, AT_RISK, HIGH, …) case-insensitively so pages can\n * pass raw enum values.\n */\nexport function statusBadgeVariant(\n status: string\n): 'default' | 'secondary' | 'destructive' | 'outline' {\n const s = status.toUpperCase();\n if (\n ['ACTIVE', 'ACHIEVED', 'ON_TRACK', 'AHEAD', 'RESOLVED', 'EXECUTED', 'APPROVED'].includes(s)\n )\n return 'default';\n if (\n ['AT_RISK', 'HIGH', 'CRITICAL', 'ABANDONED', 'REJECTED', 'CANCELLED', 'CLOSED'].includes(s)\n )\n return 'destructive';\n if (\n ['ONBOARDING', 'PROSPECT', 'ON_HOLD', 'BEHIND', 'DRAFT', 'PROPOSED', 'MEDIUM', 'LOW'].includes(\n s\n )\n )\n return 'secondary';\n return 'outline';\n}\n\n/** Semantic text-color token for a signed delta (color is never the only signal). */\nexport function deltaToneClass(value: number): string {\n if (value > 0) return 'text-status-success-text';\n if (value < 0) return 'text-status-error-text';\n return 'text-text-secondary';\n}\n\n// ============================================================================\n// Section shell + KPIs\n// ============================================================================\n\ninterface SectionShellProps {\n title: string;\n description: string;\n actionLabel?: string;\n onAction?: () => void;\n actions?: ReactNode;\n badges?: string[];\n children: ReactNode;\n}\n\nexport function SectionShell({\n title,\n description,\n actionLabel,\n onAction,\n actions,\n badges = [],\n children,\n}: SectionShellProps) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 md:flex-row md:items-start md:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">{title}</h1>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n <div className=\"flex items-center gap-2\">\n {badges.map((badge) => (\n <Badge key={badge} variant=\"secondary\">\n {badge}\n </Badge>\n ))}\n {actions}\n {actionLabel ? (\n <Button size=\"sm\" onClick={onAction}>\n {actionLabel}\n </Button>\n ) : null}\n </div>\n </div>\n {children}\n </div>\n );\n}\n\nexport interface KpiItem {\n label: string;\n value: string;\n /** Optional signed delta rendered as a DeltaBadge (icon + sign + color). */\n delta?: number;\n /** Optional free-text trend line (neutral tone unless deltaTone provided). */\n trend?: string;\n deltaTone?: 'positive' | 'negative' | 'neutral';\n}\n\nexport function KpiGrid({ items }: { items: KpiItem[] }) {\n return (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {items.map((item) => (\n <Card key={item.label}>\n <CardContent className=\"space-y-1 p-4\">\n <p className=\"text-xs text-text-secondary\">{item.label}</p>\n <p className=\"text-2xl font-semibold text-text-primary\">{item.value}</p>\n {item.delta !== undefined ? (\n <DeltaBadge value={item.delta} />\n ) : item.trend ? (\n <p\n className={\n item.deltaTone === 'positive'\n ? 'text-xs text-status-success-text'\n : item.deltaTone === 'negative'\n ? 'text-xs text-status-error-text'\n : 'text-xs text-text-secondary'\n }\n >\n {item.trend}\n </p>\n ) : null}\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Delta badge — icon + sign + semantic color (color never the only signal)\n// ============================================================================\n\nexport function DeltaBadge({\n value,\n suffix = '%',\n className = '',\n}: {\n value: number;\n suffix?: string;\n className?: string;\n}) {\n const positive = value > 0;\n const negative = value < 0;\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : Minus;\n const tone = positive\n ? 'text-status-success-text'\n : negative\n ? 'text-status-error-text'\n : 'text-text-secondary';\n const sign = positive ? '+' : '';\n return (\n <span className={`inline-flex items-center gap-1 text-xs font-medium ${tone} ${className}`}>\n <Icon className=\"size-3.5\" aria-hidden=\"true\" />\n {sign}\n {Number.isFinite(value) ? value.toFixed(1) : '—'}\n {suffix}\n </span>\n );\n}\n\n// ============================================================================\n// Allocation bars — tokenized horizontal bar primitive\n// ============================================================================\n\nexport type AllocationTone = 'default' | 'positive' | 'negative' | 'warning' | 'accent';\n\nconst TONE_FILL: Record<AllocationTone, string> = {\n default: 'bg-primary',\n positive: 'bg-status-success-bg',\n negative: 'bg-status-error-bg',\n warning: 'bg-status-warning-bg',\n accent: 'bg-accent-teal',\n};\n\nexport interface AllocationDatum {\n label: string;\n /** Percentage 0–100 for the bar width. */\n value: number;\n tone?: AllocationTone;\n /** Optional right-aligned caption (e.g. formatted money). Defaults to the %. */\n caption?: string;\n}\n\nexport function AllocationBars({ data }: { data: AllocationDatum[] }) {\n const rows = data.filter((d) => Number.isFinite(d.value));\n if (rows.length === 0) {\n return <p className=\"text-sm text-text-secondary\">No allocation data.</p>;\n }\n return (\n <div className=\"space-y-3\">\n {rows.map((d) => {\n const width = Math.max(0, Math.min(100, d.value));\n return (\n <div key={d.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-primary\">{d.label}</span>\n <span className=\"text-text-secondary\">{d.caption ?? formatPct(d.value)}</span>\n </div>\n <div\n className=\"h-2 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={d.label}\n >\n <div\n className={`h-full rounded-full ${TONE_FILL[d.tone ?? 'default']} motion-safe:transition-[width] motion-safe:duration-200`}\n style={{ width: `${width}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n\n// ============================================================================\n// Empty / Loading / Error states (designed, shared)\n// ============================================================================\n\nexport function EmptyState({\n icon,\n title,\n description,\n action,\n}: {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center rounded-lg border border-dashed border-border-subtle bg-bg-surface px-6 py-12 text-center\">\n <div className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n {icon ?? <Inbox className=\"size-6\" aria-hidden=\"true\" />}\n </div>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n {description ? (\n <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{description}</p>\n ) : null}\n {action ? <div className=\"mt-4\">{action}</div> : null}\n </div>\n );\n}\n\nexport function LoadingState({ rows = 4 }: { rows?: number }) {\n return (\n <div className=\"space-y-3\" aria-busy=\"true\" aria-live=\"polite\">\n {Array.from({ length: rows }).map((_, i) => (\n <div\n key={i}\n className=\"flex items-center gap-4 rounded-lg border border-border-subtle bg-bg-surface p-4\"\n >\n <Skeleton className=\"size-10 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-1/3\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n <Skeleton className=\"h-4 w-16\" />\n </div>\n ))}\n </div>\n );\n}\n\nexport function ErrorState({\n title = 'Something went wrong',\n description = 'We could not load this data. Please try again.',\n onRetry,\n}: {\n title?: string;\n description?: string;\n onRetry?: () => void;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center rounded-lg border border-status-error-text/40 bg-status-error-bg px-6 py-12 text-center\">\n <div className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-bg-surface text-status-error-text\">\n <TriangleAlert className=\"size-6\" aria-hidden=\"true\" />\n </div>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{description}</p>\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" className=\"mt-4\" onClick={onRetry}>\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n Retry\n </Button>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_shared.js","names":[],"sources":["../../../src/brainyrich/pages/_shared.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ArrowDownRight, ArrowUpRight, Inbox, Minus, RefreshCw, TriangleAlert } from 'lucide-react';\nimport { Badge, Button, Card, CardContent, Skeleton } from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Formatters\n// ============================================================================\n\n/** USD currency, no cents. Pass a currency code to override. */\nexport function formatMoney(value: number, currency = 'USD'): string {\n if (!Number.isFinite(value)) return '—';\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(value);\n}\n\n/** Compact money/number, e.g. 1.2M, 950K. */\nexport function formatCompact(value: number, currency?: string): string {\n if (!Number.isFinite(value)) return '—';\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n ...(currency ? { style: 'currency', currency } : {}),\n }).format(value);\n}\n\n/** Percentage from a whole-number percent (e.g. 12.5 → \"12.5%\"). */\nexport function formatPct(value: number | null | undefined, fractionDigits = 1): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '—';\n return `${value.toFixed(fractionDigits)}%`;\n}\n\n/**\n * Guarded date formatter. Only wraps a present ISO string in `new Date()` at\n * render time — never calls `.getTime()` on a raw scalar and never `new Date(0)`.\n * Returns an em-dash for absent/invalid values.\n */\nexport function formatDate(iso?: string | null): string {\n if (!iso) return '—';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n }).format(d);\n}\n\n// ============================================================================\n// Status → badge variant (semantic, no raw colors)\n// ============================================================================\n\n/**\n * Map a domain status/severity string to a shadcn Badge variant. Accepts the\n * Bra* enum members (ACTIVE, AT_RISK, HIGH, …) case-insensitively so pages can\n * pass raw enum values.\n */\nexport function statusBadgeVariant(\n status: string\n): 'default' | 'secondary' | 'destructive' | 'outline' {\n const s = status.toUpperCase();\n if (['ACTIVE', 'ACHIEVED', 'ON_TRACK', 'AHEAD', 'RESOLVED', 'EXECUTED', 'APPROVED'].includes(s))\n return 'default';\n if (['AT_RISK', 'HIGH', 'CRITICAL', 'ABANDONED', 'REJECTED', 'CANCELLED', 'CLOSED'].includes(s))\n return 'destructive';\n if (\n ['ONBOARDING', 'PROSPECT', 'ON_HOLD', 'BEHIND', 'DRAFT', 'PROPOSED', 'MEDIUM', 'LOW'].includes(\n s\n )\n )\n return 'secondary';\n return 'outline';\n}\n\n/** Semantic text-color token for a signed delta (color is never the only signal). */\nexport function deltaToneClass(value: number): string {\n if (value > 0) return 'text-status-success-text';\n if (value < 0) return 'text-status-error-text';\n return 'text-text-secondary';\n}\n\n// ============================================================================\n// Section shell + KPIs\n// ============================================================================\n\ninterface SectionShellProps {\n title: string;\n description: string;\n actionLabel?: string;\n onAction?: () => void;\n actions?: ReactNode;\n badges?: string[];\n children: ReactNode;\n}\n\nexport function SectionShell({\n title,\n description,\n actionLabel,\n onAction,\n actions,\n badges = [],\n children,\n}: SectionShellProps) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 md:flex-row md:items-start md:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">{title}</h1>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n <div className=\"flex items-center gap-2\">\n {badges.map((badge) => (\n <Badge key={badge} variant=\"secondary\">\n {badge}\n </Badge>\n ))}\n {actions}\n {actionLabel ? (\n <Button size=\"sm\" onClick={onAction}>\n {actionLabel}\n </Button>\n ) : null}\n </div>\n </div>\n {children}\n </div>\n );\n}\n\nexport interface KpiItem {\n label: string;\n value: string;\n /** Optional signed delta rendered as a DeltaBadge (icon + sign + color). */\n delta?: number;\n /** Optional free-text trend line (neutral tone unless deltaTone provided). */\n trend?: string;\n deltaTone?: 'positive' | 'negative' | 'neutral';\n}\n\nexport function KpiGrid({ items }: { items: KpiItem[] }) {\n return (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {items.map((item) => (\n <Card key={item.label}>\n <CardContent className=\"space-y-1 p-4\">\n <p className=\"text-xs text-text-secondary\">{item.label}</p>\n <p className=\"text-2xl font-semibold text-text-primary\">{item.value}</p>\n {item.delta !== undefined ? (\n <DeltaBadge value={item.delta} />\n ) : item.trend ? (\n <p\n className={\n item.deltaTone === 'positive'\n ? 'text-xs text-status-success-text'\n : item.deltaTone === 'negative'\n ? 'text-xs text-status-error-text'\n : 'text-xs text-text-secondary'\n }\n >\n {item.trend}\n </p>\n ) : null}\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Delta badge — icon + sign + semantic color (color never the only signal)\n// ============================================================================\n\nexport function DeltaBadge({\n value,\n suffix = '%',\n className = '',\n}: {\n value: number;\n suffix?: string;\n className?: string;\n}) {\n const positive = value > 0;\n const negative = value < 0;\n const Icon = positive ? ArrowUpRight : negative ? ArrowDownRight : Minus;\n const tone = positive\n ? 'text-status-success-text'\n : negative\n ? 'text-status-error-text'\n : 'text-text-secondary';\n const sign = positive ? '+' : '';\n return (\n <span className={`inline-flex items-center gap-1 text-xs font-medium ${tone} ${className}`}>\n <Icon className=\"size-3.5\" aria-hidden=\"true\" />\n {sign}\n {Number.isFinite(value) ? value.toFixed(1) : '—'}\n {suffix}\n </span>\n );\n}\n\n// ============================================================================\n// Allocation bars — tokenized horizontal bar primitive\n// ============================================================================\n\nexport type AllocationTone = 'default' | 'positive' | 'negative' | 'warning' | 'accent';\n\nconst TONE_FILL: Record<AllocationTone, string> = {\n default: 'bg-primary',\n positive: 'bg-status-success-bg',\n negative: 'bg-status-error-bg',\n warning: 'bg-status-warning-bg',\n accent: 'bg-accent-teal',\n};\n\nexport interface AllocationDatum {\n label: string;\n /** Percentage 0–100 for the bar width. */\n value: number;\n tone?: AllocationTone;\n /** Optional right-aligned caption (e.g. formatted money). Defaults to the %. */\n caption?: string;\n}\n\nexport function AllocationBars({ data }: { data: AllocationDatum[] }) {\n const rows = data.filter((d) => Number.isFinite(d.value));\n if (rows.length === 0) {\n return <p className=\"text-sm text-text-secondary\">No allocation data.</p>;\n }\n return (\n <div className=\"space-y-3\">\n {rows.map((d) => {\n const width = Math.max(0, Math.min(100, d.value));\n return (\n <div key={d.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-primary\">{d.label}</span>\n <span className=\"text-text-secondary\">{d.caption ?? formatPct(d.value)}</span>\n </div>\n <div\n className=\"h-2 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={d.label}\n >\n <div\n className={`h-full rounded-full ${TONE_FILL[d.tone ?? 'default']} motion-safe:transition-[width] motion-safe:duration-200`}\n style={{ width: `${width}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n\n// ============================================================================\n// Empty / Loading / Error states (designed, shared)\n// ============================================================================\n\nexport function EmptyState({\n icon,\n title,\n description,\n action,\n}: {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center rounded-lg border border-dashed border-border-subtle bg-bg-surface px-6 py-12 text-center\">\n <div className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n {icon ?? <Inbox className=\"size-6\" aria-hidden=\"true\" />}\n </div>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n {description ? (\n <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{description}</p>\n ) : null}\n {action ? <div className=\"mt-4\">{action}</div> : null}\n </div>\n );\n}\n\nexport function LoadingState({ rows = 4 }: { rows?: number }) {\n return (\n <div className=\"space-y-3\" aria-busy=\"true\" aria-live=\"polite\">\n {Array.from({ length: rows }).map((_, i) => (\n <div\n key={i}\n className=\"flex items-center gap-4 rounded-lg border border-border-subtle bg-bg-surface p-4\"\n >\n <Skeleton className=\"size-10 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-1/3\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n <Skeleton className=\"h-4 w-16\" />\n </div>\n ))}\n </div>\n );\n}\n\nexport function ErrorState({\n title = 'Something went wrong',\n description = 'We could not load this data. Please try again.',\n onRetry,\n}: {\n title?: string;\n description?: string;\n onRetry?: () => void;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center rounded-lg border border-status-error-text/40 bg-status-error-bg px-6 py-12 text-center\">\n <div className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-bg-surface text-status-error-text\">\n <TriangleAlert className=\"size-6\" aria-hidden=\"true\" />\n </div>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{description}</p>\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" className=\"mt-4\" onClick={onRetry}>\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n Retry\n </Button>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import e from "react";
|
|
3
|
+
import "react-dom";
|
|
4
|
+
//#region node_modules/sonner/dist/index.mjs
|
|
5
|
+
function t(e) {
|
|
6
|
+
if (!e || typeof document > "u") return;
|
|
7
|
+
let t = document.head || document.getElementsByTagName("head")[0], n = document.createElement("style");
|
|
8
|
+
n.type = "text/css", t.appendChild(n), n.styleSheet ? n.styleSheet.cssText = e : n.appendChild(document.createTextNode(e));
|
|
9
|
+
}
|
|
10
|
+
Array(12).fill(0);
|
|
11
|
+
var n = 1, r = new class {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.subscribe = (e) => (this.subscribers.push(e), () => {
|
|
14
|
+
let t = this.subscribers.indexOf(e);
|
|
15
|
+
this.subscribers.splice(t, 1);
|
|
16
|
+
}), this.publish = (e) => {
|
|
17
|
+
this.subscribers.forEach((t) => t(e));
|
|
18
|
+
}, this.addToast = (e) => {
|
|
19
|
+
this.publish(e), this.toasts = [...this.toasts, e];
|
|
20
|
+
}, this.create = (e) => {
|
|
21
|
+
let { message: t, ...r } = e, i = typeof e?.id == "number" || e.id?.length > 0 ? e.id : n++, a = this.toasts.find((e) => e.id === i), o = e.dismissible === void 0 ? !0 : e.dismissible;
|
|
22
|
+
return this.dismissedToasts.has(i) && this.dismissedToasts.delete(i), a ? this.toasts = this.toasts.map((n) => n.id === i ? (this.publish({
|
|
23
|
+
...n,
|
|
24
|
+
...e,
|
|
25
|
+
id: i,
|
|
26
|
+
title: t
|
|
27
|
+
}), {
|
|
28
|
+
...n,
|
|
29
|
+
...e,
|
|
30
|
+
id: i,
|
|
31
|
+
dismissible: o,
|
|
32
|
+
title: t
|
|
33
|
+
}) : n) : this.addToast({
|
|
34
|
+
title: t,
|
|
35
|
+
...r,
|
|
36
|
+
dismissible: o,
|
|
37
|
+
id: i
|
|
38
|
+
}), i;
|
|
39
|
+
}, this.dismiss = (e) => (e ? (this.dismissedToasts.add(e), requestAnimationFrame(() => this.subscribers.forEach((t) => t({
|
|
40
|
+
id: e,
|
|
41
|
+
dismiss: !0
|
|
42
|
+
})))) : this.toasts.forEach((e) => {
|
|
43
|
+
this.subscribers.forEach((t) => t({
|
|
44
|
+
id: e.id,
|
|
45
|
+
dismiss: !0
|
|
46
|
+
}));
|
|
47
|
+
}), e), this.message = (e, t) => this.create({
|
|
48
|
+
...t,
|
|
49
|
+
message: e
|
|
50
|
+
}), this.error = (e, t) => this.create({
|
|
51
|
+
...t,
|
|
52
|
+
message: e,
|
|
53
|
+
type: "error"
|
|
54
|
+
}), this.success = (e, t) => this.create({
|
|
55
|
+
...t,
|
|
56
|
+
type: "success",
|
|
57
|
+
message: e
|
|
58
|
+
}), this.info = (e, t) => this.create({
|
|
59
|
+
...t,
|
|
60
|
+
type: "info",
|
|
61
|
+
message: e
|
|
62
|
+
}), this.warning = (e, t) => this.create({
|
|
63
|
+
...t,
|
|
64
|
+
type: "warning",
|
|
65
|
+
message: e
|
|
66
|
+
}), this.loading = (e, t) => this.create({
|
|
67
|
+
...t,
|
|
68
|
+
type: "loading",
|
|
69
|
+
message: e
|
|
70
|
+
}), this.promise = (t, n) => {
|
|
71
|
+
if (!n) return;
|
|
72
|
+
let r;
|
|
73
|
+
n.loading !== void 0 && (r = this.create({
|
|
74
|
+
...n,
|
|
75
|
+
promise: t,
|
|
76
|
+
type: "loading",
|
|
77
|
+
message: n.loading,
|
|
78
|
+
description: typeof n.description == "function" ? void 0 : n.description
|
|
79
|
+
}));
|
|
80
|
+
let i = Promise.resolve(t instanceof Function ? t() : t), o = r !== void 0, s, c = i.then(async (t) => {
|
|
81
|
+
if (s = ["resolve", t], e.isValidElement(t)) o = !1, this.create({
|
|
82
|
+
id: r,
|
|
83
|
+
type: "default",
|
|
84
|
+
message: t
|
|
85
|
+
});
|
|
86
|
+
else if (a(t) && !t.ok) {
|
|
87
|
+
o = !1;
|
|
88
|
+
let i = typeof n.error == "function" ? await n.error(`HTTP error! status: ${t.status}`) : n.error, a = typeof n.description == "function" ? await n.description(`HTTP error! status: ${t.status}`) : n.description, s = typeof i == "object" && !e.isValidElement(i) ? i : { message: i };
|
|
89
|
+
this.create({
|
|
90
|
+
id: r,
|
|
91
|
+
type: "error",
|
|
92
|
+
description: a,
|
|
93
|
+
...s
|
|
94
|
+
});
|
|
95
|
+
} else if (t instanceof Error) {
|
|
96
|
+
o = !1;
|
|
97
|
+
let i = typeof n.error == "function" ? await n.error(t) : n.error, a = typeof n.description == "function" ? await n.description(t) : n.description, s = typeof i == "object" && !e.isValidElement(i) ? i : { message: i };
|
|
98
|
+
this.create({
|
|
99
|
+
id: r,
|
|
100
|
+
type: "error",
|
|
101
|
+
description: a,
|
|
102
|
+
...s
|
|
103
|
+
});
|
|
104
|
+
} else if (n.success !== void 0) {
|
|
105
|
+
o = !1;
|
|
106
|
+
let i = typeof n.success == "function" ? await n.success(t) : n.success, a = typeof n.description == "function" ? await n.description(t) : n.description, s = typeof i == "object" && !e.isValidElement(i) ? i : { message: i };
|
|
107
|
+
this.create({
|
|
108
|
+
id: r,
|
|
109
|
+
type: "success",
|
|
110
|
+
description: a,
|
|
111
|
+
...s
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}).catch(async (t) => {
|
|
115
|
+
if (s = ["reject", t], n.error !== void 0) {
|
|
116
|
+
o = !1;
|
|
117
|
+
let i = typeof n.error == "function" ? await n.error(t) : n.error, a = typeof n.description == "function" ? await n.description(t) : n.description, s = typeof i == "object" && !e.isValidElement(i) ? i : { message: i };
|
|
118
|
+
this.create({
|
|
119
|
+
id: r,
|
|
120
|
+
type: "error",
|
|
121
|
+
description: a,
|
|
122
|
+
...s
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}).finally(() => {
|
|
126
|
+
o && (this.dismiss(r), r = void 0), n.finally == null || n.finally.call(n);
|
|
127
|
+
}), l = () => new Promise((e, t) => c.then(() => s[0] === "reject" ? t(s[1]) : e(s[1])).catch(t));
|
|
128
|
+
return typeof r != "string" && typeof r != "number" ? { unwrap: l } : Object.assign(r, { unwrap: l });
|
|
129
|
+
}, this.custom = (e, t) => {
|
|
130
|
+
let r = t?.id || n++;
|
|
131
|
+
return this.create({
|
|
132
|
+
jsx: e(r),
|
|
133
|
+
id: r,
|
|
134
|
+
...t
|
|
135
|
+
}), r;
|
|
136
|
+
}, this.getActiveToasts = () => this.toasts.filter((e) => !this.dismissedToasts.has(e.id)), this.subscribers = [], this.toasts = [], this.dismissedToasts = /* @__PURE__ */ new Set();
|
|
137
|
+
}
|
|
138
|
+
}(), i = (e, t) => {
|
|
139
|
+
let i = t?.id || n++;
|
|
140
|
+
return r.addToast({
|
|
141
|
+
title: e,
|
|
142
|
+
...t,
|
|
143
|
+
id: i
|
|
144
|
+
}), i;
|
|
145
|
+
}, a = (e) => e && typeof e == "object" && "ok" in e && typeof e.ok == "boolean" && "status" in e && typeof e.status == "number", o = Object.assign(i, {
|
|
146
|
+
success: r.success,
|
|
147
|
+
info: r.info,
|
|
148
|
+
warning: r.warning,
|
|
149
|
+
error: r.error,
|
|
150
|
+
custom: r.custom,
|
|
151
|
+
message: r.message,
|
|
152
|
+
promise: r.promise,
|
|
153
|
+
dismiss: r.dismiss,
|
|
154
|
+
loading: r.loading
|
|
155
|
+
}, {
|
|
156
|
+
getHistory: () => r.toasts,
|
|
157
|
+
getToasts: () => r.getActiveToasts()
|
|
158
|
+
});
|
|
159
|
+
t("[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}");
|
|
160
|
+
//#endregion
|
|
161
|
+
export { o as toast };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=index.js.map
|