@burdenoff/microfe-brainyrich 2026.525.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 +55 -90
- package/dist/brainyrich/BrainyRichRoot.js.map +1 -1
- package/dist/brainyrich/BrainyRichRoutes.js +15 -35
- package/dist/brainyrich/BrainyRichRoutes.js.map +1 -1
- 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 +815 -0
- package/dist/brainyrich/hooks/useBrainyRichApi.js.map +1 -0
- package/dist/brainyrich/index.js +0 -1
- package/dist/brainyrich/pages/AccountsPage.js +743 -160
- package/dist/brainyrich/pages/AccountsPage.js.map +1 -1
- package/dist/brainyrich/pages/AiAssistantPage.js +556 -363
- package/dist/brainyrich/pages/AiAssistantPage.js.map +1 -1
- package/dist/brainyrich/pages/AnalyticsPage.js +601 -268
- package/dist/brainyrich/pages/AnalyticsPage.js.map +1 -1
- package/dist/brainyrich/pages/DashboardPage.js +375 -231
- package/dist/brainyrich/pages/DashboardPage.js.map +1 -1
- package/dist/brainyrich/pages/FamilyPage.js +599 -337
- package/dist/brainyrich/pages/FamilyPage.js.map +1 -1
- package/dist/brainyrich/pages/GoalsPage.js +810 -141
- package/dist/brainyrich/pages/GoalsPage.js.map +1 -1
- package/dist/brainyrich/pages/InvestmentsPage.js +410 -311
- package/dist/brainyrich/pages/InvestmentsPage.js.map +1 -1
- package/dist/brainyrich/pages/PortfoliosPage.js +867 -156
- package/dist/brainyrich/pages/PortfoliosPage.js.map +1 -1
- package/dist/brainyrich/pages/ReportsPage.js +421 -251
- package/dist/brainyrich/pages/ReportsPage.js.map +1 -1
- package/dist/brainyrich/pages/RiskPage.js +557 -194
- package/dist/brainyrich/pages/RiskPage.js.map +1 -1
- package/dist/brainyrich/pages/TaxPage.js +655 -221
- package/dist/brainyrich/pages/TaxPage.js.map +1 -1
- package/dist/brainyrich/pages/_shared.js +222 -41
- package/dist/brainyrich/pages/_shared.js.map +1 -1
- package/dist/index.js +2 -3
- 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
- package/dist/brainyrich/pages/AdvisorsPage.js +0 -375
- package/dist/brainyrich/pages/AdvisorsPage.js.map +0 -1
- package/dist/brainyrich/pages/AssistantPage.js +0 -332
- package/dist/brainyrich/pages/AssistantPage.js.map +0 -1
- package/dist/brainyrich/pages/EstatePage.js +0 -269
- package/dist/brainyrich/pages/EstatePage.js.map +0 -1
- package/dist/brainyrich/pages/InsurancePage.js +0 -181
- package/dist/brainyrich/pages/InsurancePage.js.map +0 -1
- package/dist/brainyrich/pages/MarketplacePage.js +0 -450
- package/dist/brainyrich/pages/MarketplacePage.js.map +0 -1
- package/dist/brainyrich/store/brainyrichStore.js +0 -272
- package/dist/brainyrich/store/brainyrichStore.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaxPage.js","names":[],"sources":["../../../src/brainyrich/pages/TaxPage.tsx"],"sourcesContent":["import {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n CardDescription,\n Badge,\n Button,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { Calculator, FileText, Upload, Clock, CheckCircle, AlertTriangle } from 'lucide-react';\nimport { useBrainyRichStore } from '../store/brainyrichStore';\nimport { SectionShell, KpiGrid, statusBadgeVariant } from './_shared';\n\nconst taxStrategies = [\n {\n id: 'ts1',\n strategy: 'Section 80C Investments',\n limit: 150000,\n utilized: 142000,\n status: 'Almost Full',\n },\n {\n id: 'ts2',\n strategy: 'NPS Contribution (80CCD)',\n limit: 50000,\n utilized: 50000,\n status: 'Fully Utilized',\n },\n {\n id: 'ts3',\n strategy: 'Health Insurance (80D)',\n limit: 75000,\n utilized: 52600,\n status: 'Available',\n },\n {\n id: 'ts4',\n strategy: 'HRA Exemption',\n limit: 240000,\n utilized: 240000,\n status: 'Fully Utilized',\n },\n];\n\nexport function TaxPage() {\n const { taxDocs } = useBrainyRichStore();\n\n const uploadedCount = taxDocs.filter((d) => d.status === 'Uploaded').length;\n const pendingCount = taxDocs.filter((d) => d.status === 'Pending').length;\n\n return (\n <SectionShell\n title=\"Tax Planning & Compliance\"\n description=\"Optimize your tax strategy, track documents, and ensure regulatory compliance\"\n actionLabel=\"Upload Document\"\n badges={['FY 2024-25']}\n >\n <KpiGrid\n items={[\n { label: 'Estimated Tax Liability', value: '$18,400', trend: 'After deductions' },\n { label: 'Total Deductions', value: '$28,500', trend: 'Across all sections' },\n { label: 'Effective Tax Rate', value: '18.5%', trend: 'Below bracket avg' },\n { label: 'Tax-Loss Harvesting', value: '$7,200', trend: 'Potential savings' },\n ]}\n />\n\n <Tabs defaultValue=\"documents\" className=\"space-y-4\">\n <TabsList className=\"grid w-full grid-cols-3\">\n <TabsTrigger value=\"documents\">Documents</TabsTrigger>\n <TabsTrigger value=\"deductions\">Deduction Planner</TabsTrigger>\n <TabsTrigger value=\"actions\">Tax Actions</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"documents\">\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <div>\n <CardTitle className=\"flex items-center gap-2\">\n <FileText className=\"h-5 w-5\" />\n Tax Documents\n </CardTitle>\n <CardDescription>\n {uploadedCount} uploaded, {pendingCount} pending\n </CardDescription>\n </div>\n <Button size=\"sm\">\n <Upload className=\"mr-1 h-4 w-4\" />\n Upload\n </Button>\n </CardHeader>\n <CardContent>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Document</TableHead>\n <TableHead>Tax Year</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"text-right\">Action</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {taxDocs.map((d) => (\n <TableRow key={d.id}>\n <TableCell className=\"font-medium\">{d.name}</TableCell>\n <TableCell>{d.taxYear}</TableCell>\n <TableCell>\n <Badge variant={statusBadgeVariant(d.status)}>\n {d.status === 'Uploaded' ? (\n <>\n <CheckCircle className=\"mr-1 h-3 w-3\" />\n {d.status}\n </>\n ) : (\n <>\n <Clock className=\"mr-1 h-3 w-3\" />\n {d.status}\n </>\n )}\n </Badge>\n </TableCell>\n <TableCell className=\"text-right\">\n <Button variant=\"outline\" size=\"sm\">\n {d.status === 'Uploaded' ? 'View' : 'Upload'}\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"deductions\" className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Calculator className=\"h-5 w-5\" />\n Deduction Utilization\n </CardTitle>\n <CardDescription>Track your progress across all tax-saving sections</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {taxStrategies.map((s) => {\n const pct = Math.round((s.utilized / s.limit) * 100);\n const remaining = s.limit - s.utilized;\n return (\n <div key={s.id} className=\"space-y-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium text-sm\">{s.strategy}</span>\n <Badge\n variant={pct >= 100 ? 'default' : pct >= 80 ? 'secondary' : 'destructive'}\n >\n {s.status}\n </Badge>\n </div>\n <div className=\"h-3 rounded bg-muted overflow-hidden\">\n <div\n className={`h-3 rounded ${pct >= 100 ? 'bg-emerald-500' : 'bg-blue-500'}`}\n style={{ width: `${Math.min(pct, 100)}%` }}\n />\n </div>\n <div className=\"flex justify-between text-xs text-muted-foreground\">\n <span>\n ${s.utilized.toLocaleString()} / ${s.limit.toLocaleString()}\n </span>\n <span>\n {remaining > 0\n ? `$${remaining.toLocaleString()} available`\n : 'Fully utilized'}\n </span>\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"actions\" className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n {[\n {\n icon: CheckCircle,\n title: 'Maximize 80C Investments',\n desc: 'You can invest $8,000 more in ELSS funds this year',\n color: 'bg-emerald-50 border-emerald-200',\n iconColor: 'text-emerald-600',\n action: 'Invest Now',\n },\n {\n icon: Calculator,\n title: 'Tax-Loss Harvesting',\n desc: 'Potential $7,200 savings from unrealized losses in portfolio',\n color: 'bg-purple-50 border-purple-200',\n iconColor: 'text-purple-600',\n action: 'Analyze Portfolio',\n },\n {\n icon: AlertTriangle,\n title: 'Advance Tax Deadline',\n desc: 'Next quarterly advance tax installment of $4,600 due June 15',\n color: 'bg-yellow-50 border-yellow-200',\n iconColor: 'text-yellow-600',\n action: 'Schedule Payment',\n },\n {\n icon: FileText,\n title: 'Review Health Insurance',\n desc: 'Optimize Section 80D benefits - $22,400 still available',\n color: 'bg-blue-50 border-blue-200',\n iconColor: 'text-blue-600',\n action: 'Review Benefits',\n },\n ].map((item) => (\n <Card key={item.title} className={`${item.color} border`}>\n <CardContent className=\"flex items-start gap-3 p-4\">\n <item.icon className={`h-5 w-5 mt-0.5 ${item.iconColor}`} />\n <div className=\"flex-1\">\n <p className=\"font-medium text-sm\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{item.desc}</p>\n <Button variant=\"outline\" size=\"sm\" className=\"mt-2\">\n {item.action}\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </TabsContent>\n </Tabs>\n </SectionShell>\n );\n}\n"],"mappings":";;;;;;AAuBA,IAAM,IAAgB;CACpB;EACE,IAAI;EACJ,UAAU;EACV,OAAO;EACP,UAAU;EACV,QAAQ;EACT;CACD;EACE,IAAI;EACJ,UAAU;EACV,OAAO;EACP,UAAU;EACV,QAAQ;EACT;CACD;EACE,IAAI;EACJ,UAAU;EACV,OAAO;EACP,UAAU;EACV,QAAQ;EACT;CACD;EACE,IAAI;EACJ,UAAU;EACV,OAAO;EACP,UAAU;EACV,QAAQ;EACT;CACF;AAED,SAAgB,IAAU;CACxB,IAAM,EAAE,eAAY,GAAoB,EAElC,IAAgB,EAAQ,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC,QAC/D,IAAe,EAAQ,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC;AAEnE,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,aAAY;EACZ,QAAQ,CAAC,aAAa;YAJxB,CAME,kBAAC,GAAD,EACE,OAAO;GACL;IAAE,OAAO;IAA2B,OAAO;IAAW,OAAO;IAAoB;GACjF;IAAE,OAAO;IAAoB,OAAO;IAAW,OAAO;IAAuB;GAC7E;IAAE,OAAO;IAAsB,OAAO;IAAS,OAAO;IAAqB;GAC3E;IAAE,OAAO;IAAuB,OAAO;IAAU,OAAO;IAAqB;GAC9E,EACD,CAAA,EAEF,kBAAC,GAAD;GAAM,cAAa;GAAY,WAAU;aAAzC;IACE,kBAAC,GAAD;KAAU,WAAU;eAApB;MACE,kBAAC,GAAD;OAAa,OAAM;iBAAY;OAAuB,CAAA;MACtD,kBAAC,GAAD;OAAa,OAAM;iBAAa;OAA+B,CAAA;MAC/D,kBAAC,GAAD;OAAa,OAAM;iBAAU;OAAyB,CAAA;MAC7C;;IAEX,kBAAC,GAAD;KAAa,OAAM;eACjB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,WAAU;gBAAtB,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAW,WAAU;iBAArB,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,gBAEtB;UACZ,kBAAC,GAAD,EAAA,UAAA;OACG;OAAc;OAAY;OAAa;OACxB,EAAA,CAAA,CACd,EAAA,CAAA,EACN,kBAAC,GAAD;OAAQ,MAAK;iBAAb,CACE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,SAE5B;SACE;SACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;MAC/B,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA;MAC/B,kBAAC,GAAD,EAAA,UAAW,UAAkB,CAAA;MAC7B,kBAAC,GAAD;OAAW,WAAU;iBAAa;OAAkB,CAAA;MAC3C,EAAA,CAAA,EACC,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD;OAAW,WAAU;iBAAe,EAAE;OAAiB,CAAA;MACvD,kBAAC,GAAD,EAAA,UAAY,EAAE,SAAoB,CAAA;MAClC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;OAAO,SAAS,EAAmB,EAAE,OAAO;iBACzC,EAAE,WAAW,aACZ,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,WAAU,gBAAiB,CAAA,EACvC,EAAE,OACF,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EACjC,EAAE,OACF,EAAA,CAAA;OAEC,CAAA,EACE,CAAA;MACZ,kBAAC,GAAD;OAAW,WAAU;iBACnB,kBAAC,GAAD;QAAQ,SAAQ;QAAU,MAAK;kBAC5B,EAAE,WAAW,aAAa,SAAS;QAC7B,CAAA;OACC,CAAA;MACH,EAAA,EAvBI,EAAE,GAuBN,CACX,EACQ,CAAA,CACN,EAAA,CAAA,EACI,CAAA,CACT,EAAA,CAAA;KACK,CAAA;IAEd,kBAAC,GAAD;KAAa,OAAM;KAAa,WAAU;eACxC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,wBAExB;SACZ,kBAAC,GAAD,EAAA,UAAiB,sDAAoE,CAAA,CAC1E,EAAA,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBACpB,EAAc,KAAK,MAAM;OACxB,IAAM,IAAM,KAAK,MAAO,EAAE,WAAW,EAAE,QAAS,IAAI,EAC9C,IAAY,EAAE,QAAQ,EAAE;AAC9B,cACE,kBAAC,OAAD;QAAgB,WAAU;kBAA1B;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAuB,EAAE;WAAgB,CAAA,EACzD,kBAAC,GAAD;WACE,SAAS,KAAO,MAAM,YAAY,KAAO,KAAK,cAAc;qBAE3D,EAAE;WACG,CAAA,CACJ;;SACN,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,OAAD;WACE,WAAW,eAAe,KAAO,MAAM,mBAAmB;WAC1D,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,GAAK,IAAI,CAAC,IAAI;WAC1C,CAAA;UACE,CAAA;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;WAAM;WACF,EAAE,SAAS,gBAAgB;WAAC;WAAK,EAAE,MAAM,gBAAgB;WACtD,EAAA,CAAA,EACP,kBAAC,QAAD,EAAA,UACG,IAAY,IACT,IAAI,EAAU,gBAAgB,CAAC,cAC/B,kBACC,CAAA,CACH;;SACF;UAzBI,EAAE,GAyBN;QAER;MACU,CAAA,CACT,EAAA,CAAA;KACK,CAAA;IAEd,kBAAC,GAAD;KAAa,OAAM;KAAU,WAAU;eACrC,kBAAC,OAAD;MAAK,WAAU;gBACZ;OACC;QACE,MAAM;QACN,OAAO;QACP,MAAM;QACN,OAAO;QACP,WAAW;QACX,QAAQ;QACT;OACD;QACE,MAAM;QACN,OAAO;QACP,MAAM;QACN,OAAO;QACP,WAAW;QACX,QAAQ;QACT;OACD;QACE,MAAM;QACN,OAAO;QACP,MAAM;QACN,OAAO;QACP,WAAW;QACX,QAAQ;QACT;OACD;QACE,MAAM;QACN,OAAO;QACP,MAAM;QACN,OAAO;QACP,WAAW;QACX,QAAQ;QACT;OACF,CAAC,KAAK,MACL,kBAAC,GAAD;OAAuB,WAAW,GAAG,EAAK,MAAM;iBAC9C,kBAAC,GAAD;QAAa,WAAU;kBAAvB,CACE,kBAAC,EAAK,MAAN,EAAW,WAAW,kBAAkB,EAAK,aAAe,CAAA,EAC5D,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAuB,EAAK;WAAU,CAAA;UACnD,kBAAC,KAAD;WAAG,WAAU;qBAAsC,EAAK;WAAS,CAAA;UACjE,kBAAC,GAAD;WAAQ,SAAQ;WAAU,MAAK;WAAK,WAAU;qBAC3C,EAAK;WACC,CAAA;UACL;WACM;;OACT,EAXI,EAAK,MAWT,CACP;MACE,CAAA;KACM,CAAA;IACT;KACM"}
|
|
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,63 +1,244 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ArrowDownRight as e, ArrowUpRight as t, Inbox as n, Minus as r, RefreshCw as i, TriangleAlert as a } from "lucide-react";
|
|
2
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
3
|
+
import { Badge as c, Button as l, Card as u, CardContent as d, Skeleton as f } from "@burdenoff/fe-libs/ui";
|
|
3
4
|
//#region src/brainyrich/pages/_shared.tsx
|
|
4
|
-
function
|
|
5
|
-
return new Intl.NumberFormat("en-US", {
|
|
5
|
+
function p(e, t = "USD") {
|
|
6
|
+
return Number.isFinite(e) ? new Intl.NumberFormat("en-US", {
|
|
6
7
|
style: "currency",
|
|
7
|
-
currency:
|
|
8
|
+
currency: t,
|
|
8
9
|
maximumFractionDigits: 0
|
|
9
|
-
}).format(e);
|
|
10
|
+
}).format(e) : "—";
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
-
return e
|
|
12
|
+
function m(e, t = 1) {
|
|
13
|
+
return e == null || !Number.isFinite(e) ? "—" : `${e.toFixed(t)}%`;
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
15
|
+
function h(e) {
|
|
16
|
+
if (!e) return "—";
|
|
17
|
+
let t = new Date(e);
|
|
18
|
+
return Number.isNaN(t.getTime()) ? "—" : new Intl.DateTimeFormat("en-US", {
|
|
19
|
+
year: "numeric",
|
|
20
|
+
month: "short",
|
|
21
|
+
day: "numeric"
|
|
22
|
+
}).format(t);
|
|
23
|
+
}
|
|
24
|
+
function g(e) {
|
|
25
|
+
let t = e.toUpperCase();
|
|
26
|
+
return [
|
|
27
|
+
"ACTIVE",
|
|
28
|
+
"ACHIEVED",
|
|
29
|
+
"ON_TRACK",
|
|
30
|
+
"AHEAD",
|
|
31
|
+
"RESOLVED",
|
|
32
|
+
"EXECUTED",
|
|
33
|
+
"APPROVED"
|
|
34
|
+
].includes(t) ? "default" : [
|
|
35
|
+
"AT_RISK",
|
|
36
|
+
"HIGH",
|
|
37
|
+
"CRITICAL",
|
|
38
|
+
"ABANDONED",
|
|
39
|
+
"REJECTED",
|
|
40
|
+
"CANCELLED",
|
|
41
|
+
"CLOSED"
|
|
42
|
+
].includes(t) ? "destructive" : [
|
|
43
|
+
"ONBOARDING",
|
|
44
|
+
"PROSPECT",
|
|
45
|
+
"ON_HOLD",
|
|
46
|
+
"BEHIND",
|
|
47
|
+
"DRAFT",
|
|
48
|
+
"PROPOSED",
|
|
49
|
+
"MEDIUM",
|
|
50
|
+
"LOW"
|
|
51
|
+
].includes(t) ? "secondary" : "outline";
|
|
52
|
+
}
|
|
53
|
+
function _(e) {
|
|
54
|
+
return e > 0 ? "text-status-success-text" : e < 0 ? "text-status-error-text" : "text-text-secondary";
|
|
55
|
+
}
|
|
56
|
+
function v({ title: e, description: t, actionLabel: n, onAction: r, actions: i, badges: a = [], children: u }) {
|
|
57
|
+
return /* @__PURE__ */ s("div", {
|
|
16
58
|
className: "space-y-6 p-6",
|
|
17
|
-
children: [/* @__PURE__ */
|
|
59
|
+
children: [/* @__PURE__ */ s("div", {
|
|
18
60
|
className: "flex flex-col gap-4 md:flex-row md:items-start md:justify-between",
|
|
19
|
-
children: [/* @__PURE__ */
|
|
20
|
-
className: "text-2xl font-semibold",
|
|
21
|
-
children:
|
|
22
|
-
}), /* @__PURE__ */
|
|
23
|
-
className: "text-sm text-
|
|
24
|
-
children:
|
|
25
|
-
})] }), /* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ s("div", { children: [/* @__PURE__ */ o("h1", {
|
|
62
|
+
className: "text-2xl font-semibold text-text-primary",
|
|
63
|
+
children: e
|
|
64
|
+
}), /* @__PURE__ */ o("p", {
|
|
65
|
+
className: "text-sm text-text-secondary",
|
|
66
|
+
children: t
|
|
67
|
+
})] }), /* @__PURE__ */ s("div", {
|
|
26
68
|
className: "flex items-center gap-2",
|
|
27
|
-
children: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
69
|
+
children: [
|
|
70
|
+
a.map((e) => /* @__PURE__ */ o(c, {
|
|
71
|
+
variant: "secondary",
|
|
72
|
+
children: e
|
|
73
|
+
}, e)),
|
|
74
|
+
i,
|
|
75
|
+
n ? /* @__PURE__ */ o(l, {
|
|
76
|
+
size: "sm",
|
|
77
|
+
onClick: r,
|
|
78
|
+
children: n
|
|
79
|
+
}) : null
|
|
80
|
+
]
|
|
34
81
|
})]
|
|
35
|
-
}),
|
|
82
|
+
}), u]
|
|
36
83
|
});
|
|
37
84
|
}
|
|
38
|
-
function
|
|
39
|
-
return /* @__PURE__ */
|
|
85
|
+
function y({ items: e }) {
|
|
86
|
+
return /* @__PURE__ */ o("div", {
|
|
40
87
|
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
|
|
41
|
-
children:
|
|
88
|
+
children: e.map((e) => /* @__PURE__ */ o(u, { children: /* @__PURE__ */ s(d, {
|
|
42
89
|
className: "space-y-1 p-4",
|
|
43
90
|
children: [
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
className: "text-xs text-
|
|
46
|
-
children:
|
|
91
|
+
/* @__PURE__ */ o("p", {
|
|
92
|
+
className: "text-xs text-text-secondary",
|
|
93
|
+
children: e.label
|
|
94
|
+
}),
|
|
95
|
+
/* @__PURE__ */ o("p", {
|
|
96
|
+
className: "text-2xl font-semibold text-text-primary",
|
|
97
|
+
children: e.value
|
|
47
98
|
}),
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
className: "text-
|
|
50
|
-
children:
|
|
99
|
+
e.delta === void 0 ? e.trend ? /* @__PURE__ */ o("p", {
|
|
100
|
+
className: e.deltaTone === "positive" ? "text-xs text-status-success-text" : e.deltaTone === "negative" ? "text-xs text-status-error-text" : "text-xs text-text-secondary",
|
|
101
|
+
children: e.trend
|
|
102
|
+
}) : null : /* @__PURE__ */ o(b, { value: e.delta })
|
|
103
|
+
]
|
|
104
|
+
}) }, e.label))
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function b({ value: n, suffix: i = "%", className: a = "" }) {
|
|
108
|
+
let c = n > 0, l = n < 0, u = c ? t : l ? e : r, d = c ? "text-status-success-text" : l ? "text-status-error-text" : "text-text-secondary", f = c ? "+" : "";
|
|
109
|
+
return /* @__PURE__ */ s("span", {
|
|
110
|
+
className: `inline-flex items-center gap-1 text-xs font-medium ${d} ${a}`,
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ o(u, {
|
|
113
|
+
className: "size-3.5",
|
|
114
|
+
"aria-hidden": "true"
|
|
115
|
+
}),
|
|
116
|
+
f,
|
|
117
|
+
Number.isFinite(n) ? n.toFixed(1) : "—",
|
|
118
|
+
i
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
var x = {
|
|
123
|
+
default: "bg-primary",
|
|
124
|
+
positive: "bg-status-success-bg",
|
|
125
|
+
negative: "bg-status-error-bg",
|
|
126
|
+
warning: "bg-status-warning-bg",
|
|
127
|
+
accent: "bg-accent-teal"
|
|
128
|
+
};
|
|
129
|
+
function S({ data: e }) {
|
|
130
|
+
let t = e.filter((e) => Number.isFinite(e.value));
|
|
131
|
+
return t.length === 0 ? /* @__PURE__ */ o("p", {
|
|
132
|
+
className: "text-sm text-text-secondary",
|
|
133
|
+
children: "No allocation data."
|
|
134
|
+
}) : /* @__PURE__ */ o("div", {
|
|
135
|
+
className: "space-y-3",
|
|
136
|
+
children: t.map((e) => {
|
|
137
|
+
let t = Math.max(0, Math.min(100, e.value));
|
|
138
|
+
return /* @__PURE__ */ s("div", {
|
|
139
|
+
className: "space-y-1",
|
|
140
|
+
children: [/* @__PURE__ */ s("div", {
|
|
141
|
+
className: "flex items-center justify-between text-xs",
|
|
142
|
+
children: [/* @__PURE__ */ o("span", {
|
|
143
|
+
className: "text-text-primary",
|
|
144
|
+
children: e.label
|
|
145
|
+
}), /* @__PURE__ */ o("span", {
|
|
146
|
+
className: "text-text-secondary",
|
|
147
|
+
children: e.caption ?? m(e.value)
|
|
148
|
+
})]
|
|
149
|
+
}), /* @__PURE__ */ o("div", {
|
|
150
|
+
className: "h-2 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
151
|
+
role: "progressbar",
|
|
152
|
+
"aria-valuenow": Math.round(t),
|
|
153
|
+
"aria-valuemin": 0,
|
|
154
|
+
"aria-valuemax": 100,
|
|
155
|
+
"aria-label": e.label,
|
|
156
|
+
children: /* @__PURE__ */ o("div", {
|
|
157
|
+
className: `h-full rounded-full ${x[e.tone ?? "default"]} motion-safe:transition-[width] motion-safe:duration-200`,
|
|
158
|
+
style: { width: `${t}%` }
|
|
159
|
+
})
|
|
160
|
+
})]
|
|
161
|
+
}, e.label);
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function C({ icon: e, title: t, description: r, action: i }) {
|
|
166
|
+
return /* @__PURE__ */ s("div", {
|
|
167
|
+
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",
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ o("div", {
|
|
170
|
+
className: "mb-4 flex size-12 items-center justify-center rounded-full bg-bg-sunken text-text-secondary",
|
|
171
|
+
children: e ?? /* @__PURE__ */ o(n, {
|
|
172
|
+
className: "size-6",
|
|
173
|
+
"aria-hidden": "true"
|
|
174
|
+
})
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ o("h3", {
|
|
177
|
+
className: "text-base font-semibold text-text-primary",
|
|
178
|
+
children: t
|
|
179
|
+
}),
|
|
180
|
+
r ? /* @__PURE__ */ o("p", {
|
|
181
|
+
className: "mt-1 max-w-md text-sm text-text-secondary",
|
|
182
|
+
children: r
|
|
183
|
+
}) : null,
|
|
184
|
+
i ? /* @__PURE__ */ o("div", {
|
|
185
|
+
className: "mt-4",
|
|
186
|
+
children: i
|
|
187
|
+
}) : null
|
|
188
|
+
]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function w({ rows: e = 4 }) {
|
|
192
|
+
return /* @__PURE__ */ o("div", {
|
|
193
|
+
className: "space-y-3",
|
|
194
|
+
"aria-busy": "true",
|
|
195
|
+
"aria-live": "polite",
|
|
196
|
+
children: Array.from({ length: e }).map((e, t) => /* @__PURE__ */ s("div", {
|
|
197
|
+
className: "flex items-center gap-4 rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ o(f, { className: "size-10 rounded-full" }),
|
|
200
|
+
/* @__PURE__ */ s("div", {
|
|
201
|
+
className: "flex-1 space-y-2",
|
|
202
|
+
children: [/* @__PURE__ */ o(f, { className: "h-4 w-1/3" }), /* @__PURE__ */ o(f, { className: "h-3 w-1/2" })]
|
|
51
203
|
}),
|
|
52
|
-
|
|
53
|
-
className: "text-xs text-emerald-600",
|
|
54
|
-
children: n.trend
|
|
55
|
-
}) : null
|
|
204
|
+
/* @__PURE__ */ o(f, { className: "h-4 w-16" })
|
|
56
205
|
]
|
|
57
|
-
}
|
|
206
|
+
}, t))
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function T({ title: e = "Something went wrong", description: t = "We could not load this data. Please try again.", onRetry: n }) {
|
|
210
|
+
return /* @__PURE__ */ s("div", {
|
|
211
|
+
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",
|
|
212
|
+
children: [
|
|
213
|
+
/* @__PURE__ */ o("div", {
|
|
214
|
+
className: "mb-4 flex size-12 items-center justify-center rounded-full bg-bg-surface text-status-error-text",
|
|
215
|
+
children: /* @__PURE__ */ o(a, {
|
|
216
|
+
className: "size-6",
|
|
217
|
+
"aria-hidden": "true"
|
|
218
|
+
})
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ o("h3", {
|
|
221
|
+
className: "text-base font-semibold text-text-primary",
|
|
222
|
+
children: e
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ o("p", {
|
|
225
|
+
className: "mt-1 max-w-md text-sm text-text-secondary",
|
|
226
|
+
children: t
|
|
227
|
+
}),
|
|
228
|
+
n ? /* @__PURE__ */ s(l, {
|
|
229
|
+
variant: "outline",
|
|
230
|
+
size: "sm",
|
|
231
|
+
className: "mt-4",
|
|
232
|
+
onClick: n,
|
|
233
|
+
children: [/* @__PURE__ */ o(i, {
|
|
234
|
+
className: "size-4",
|
|
235
|
+
"aria-hidden": "true"
|
|
236
|
+
}), "Retry"]
|
|
237
|
+
}) : null
|
|
238
|
+
]
|
|
58
239
|
});
|
|
59
240
|
}
|
|
60
241
|
//#endregion
|
|
61
|
-
export {
|
|
242
|
+
export { S as AllocationBars, C as EmptyState, T as ErrorState, y as KpiGrid, w as LoadingState, v as SectionShell, _ as deltaToneClass, h as formatDate, p as formatMoney, m as formatPct, g as statusBadgeVariant };
|
|
62
243
|
|
|
63
244
|
//# sourceMappingURL=_shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_shared.js","names":[],"sources":["../../../src/brainyrich/pages/_shared.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Badge, Button, Card, CardContent } from '@burdenoff/fe-libs/ui';\n\nexport function formatMoney(value: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n maximumFractionDigits: 0,\n }).format(value);\n}\n\nexport function statusBadgeVariant(\n status: string\n): 'default' | 'secondary' | 'destructive' | 'outline' {\n if (status === 'active' || status === 'Connected' || status === 'Uploaded') return 'default';\n if (status === 'at_risk' || status === 'High' || status === 'Pending') return 'destructive';\n if (status === 'planned' || status === 'Medium' || status === 'Sync Needed') return 'secondary';\n return 'outline';\n}\n\ninterface SectionShellProps {\n title: string;\n description: string;\n actionLabel?: string;\n badges?: string[];\n children: ReactNode;\n}\n\nexport function SectionShell({\n title,\n description,\n actionLabel,\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\">{title}</h1>\n <p className=\"text-sm text-muted-foreground\">{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 {actionLabel ? <Button size=\"sm\">{actionLabel}</Button> : null}\n </div>\n </div>\n {children}\n </div>\n );\n}\n\nexport function KpiGrid({\n items,\n}: {\n items: Array<{ label: string; value: string; trend?: string }>;\n}) {\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-muted-foreground\">{item.label}</p>\n <p className=\"text-2xl font-semibold\">{item.value}</p>\n {item.trend ? <p className=\"text-xs text-emerald-600\">{item.trend}</p> : null}\n </CardContent>\n </Card>\n ))}\n </div>\n );\n}\n"],"mappings":";;;AAGA,SAAgB,EAAY,GAAuB;AACjD,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACV,uBAAuB;EACxB,CAAC,CAAC,OAAO,EAAM;;AAGlB,SAAgB,EACd,GACqD;AAIrD,QAHI,MAAW,YAAY,MAAW,eAAe,MAAW,aAAmB,YAC/E,MAAW,aAAa,MAAW,UAAU,MAAW,YAAkB,gBAC1E,MAAW,aAAa,MAAW,YAAY,MAAW,gBAAsB,cAC7E;;AAWT,SAAgB,EAAa,EAC3B,UACA,gBACA,gBACA,YAAS,EAAE,EACX,eACoB;AACpB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAA0B;IAAW,CAAA,EACnD,kBAAC,KAAD;IAAG,WAAU;cAAiC;IAAgB,CAAA,CAC1D,EAAA,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAO,KAAK,MACX,kBAAC,GAAD;KAAmB,SAAQ;eACxB;KACK,EAFI,EAEJ,CACR,EACD,IAAc,kBAAC,GAAD;KAAQ,MAAK;eAAM;KAAqB,CAAA,GAAG,KACtD;MACF;MACL,EACG;;;AAIV,SAAgB,EAAQ,EACtB,YAGC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAM,KAAK,MACV,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACE,kBAAC,KAAD;KAAG,WAAU;eAAiC,EAAK;KAAU,CAAA;IAC7D,kBAAC,KAAD;KAAG,WAAU;eAA0B,EAAK;KAAU,CAAA;IACrD,EAAK,QAAQ,kBAAC,KAAD;KAAG,WAAU;eAA4B,EAAK;KAAU,CAAA,GAAG;IAC7D;MACT,EANI,EAAK,MAMT,CACP;EACE,CAAA"}
|
|
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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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useBrainyRich as e } from "./brainyrich/providers/BrainyRichProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import { BrainyRichRoot as n } from "./brainyrich/BrainyRichRoot.js";
|
|
2
|
+
import { BrainyRichRoot as t } from "./brainyrich/BrainyRichRoot.js";
|
|
4
3
|
import "./brainyrich/index.js";
|
|
5
|
-
export {
|
|
4
|
+
export { t as BrainyRichRoot, e as useBrainyRich };
|