@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,7 +1,7 @@
|
|
|
1
1
|
import { BrainyRichProvider as e } from "./providers/BrainyRichProvider.js";
|
|
2
2
|
import { tWithFallback as t } from "./utils/i18n.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { joinPath as n } from "./utils/navigation.js";
|
|
4
|
+
import { BrainyRichRoutes as r } from "./BrainyRichRoutes.js";
|
|
5
5
|
import { useEffect as i } from "react";
|
|
6
6
|
import { BarChart3 as a, Bot as o, Briefcase as s, Calculator as c, FileBarChart as l, Landmark as u, LayoutDashboard as d, ShieldAlert as f, Target as p, TrendingUp as m, UsersRound as h } from "lucide-react";
|
|
7
7
|
import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
@@ -29,70 +29,70 @@ var y = (y) => {
|
|
|
29
29
|
{
|
|
30
30
|
id: "brainyrich-portfolios",
|
|
31
31
|
label: w("brainyrich.nav.portfolios", "Portfolios"),
|
|
32
|
-
path:
|
|
32
|
+
path: n(S, "portfolios"),
|
|
33
33
|
icon: /* @__PURE__ */ _(s, { size: 16 }),
|
|
34
34
|
order: 10
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
id: "brainyrich-investments",
|
|
38
38
|
label: w("brainyrich.nav.investments", "Investments"),
|
|
39
|
-
path:
|
|
39
|
+
path: n(S, "investments"),
|
|
40
40
|
icon: /* @__PURE__ */ _(m, { size: 16 }),
|
|
41
41
|
order: 11
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
id: "brainyrich-goals",
|
|
45
45
|
label: w("brainyrich.nav.goals", "Goals"),
|
|
46
|
-
path:
|
|
46
|
+
path: n(S, "goals"),
|
|
47
47
|
icon: /* @__PURE__ */ _(p, { size: 16 }),
|
|
48
48
|
order: 12
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
id: "brainyrich-accounts",
|
|
52
52
|
label: w("brainyrich.nav.accounts", "Accounts"),
|
|
53
|
-
path:
|
|
53
|
+
path: n(S, "accounts"),
|
|
54
54
|
icon: /* @__PURE__ */ _(u, { size: 16 }),
|
|
55
55
|
order: 13
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
id: "brainyrich-analytics",
|
|
59
59
|
label: w("brainyrich.nav.analytics", "Analytics"),
|
|
60
|
-
path:
|
|
60
|
+
path: n(S, "analytics"),
|
|
61
61
|
icon: /* @__PURE__ */ _(a, { size: 16 }),
|
|
62
62
|
order: 21
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
id: "brainyrich-risk",
|
|
66
66
|
label: w("brainyrich.nav.risk", "Rebalancing"),
|
|
67
|
-
path:
|
|
67
|
+
path: n(S, "risk"),
|
|
68
68
|
icon: /* @__PURE__ */ _(f, { size: 16 }),
|
|
69
69
|
order: 22
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
id: "brainyrich-tax",
|
|
73
73
|
label: w("brainyrich.nav.tax", "Tax"),
|
|
74
|
-
path:
|
|
74
|
+
path: n(S, "tax"),
|
|
75
75
|
icon: /* @__PURE__ */ _(c, { size: 16 }),
|
|
76
76
|
order: 30
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
id: "brainyrich-family",
|
|
80
80
|
label: w("brainyrich.nav.family", "Households"),
|
|
81
|
-
path:
|
|
81
|
+
path: n(S, "family"),
|
|
82
82
|
icon: /* @__PURE__ */ _(h, { size: 16 }),
|
|
83
83
|
order: 40
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
id: "brainyrich-reports",
|
|
87
87
|
label: w("brainyrich.nav.reports", "Reports"),
|
|
88
|
-
path:
|
|
88
|
+
path: n(S, "reports"),
|
|
89
89
|
icon: /* @__PURE__ */ _(l, { size: 16 }),
|
|
90
90
|
order: 42
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
id: "brainyrich-ai-assistant",
|
|
94
94
|
label: w("brainyrich.nav.aiAssistant", "Insights"),
|
|
95
|
-
path:
|
|
95
|
+
path: n(S, "ai-assistant"),
|
|
96
96
|
icon: /* @__PURE__ */ _(o, { size: 16 }),
|
|
97
97
|
order: 50
|
|
98
98
|
}
|
|
@@ -116,7 +116,7 @@ var y = (y) => {
|
|
|
116
116
|
defaultEnabled: !0,
|
|
117
117
|
children: /* @__PURE__ */ _(e, {
|
|
118
118
|
...y,
|
|
119
|
-
children: /* @__PURE__ */ _(
|
|
119
|
+
children: /* @__PURE__ */ _(r, {})
|
|
120
120
|
})
|
|
121
121
|
});
|
|
122
122
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useBrainyRich as e } from "../providers/BrainyRichProvider.js";
|
|
2
|
+
import { useMemo as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { useActorProfiles as i } from "@burdenoff/fe-libs/shared";
|
|
5
|
+
//#region src/brainyrich/components/ActorName.tsx
|
|
6
|
+
function a({ id: a, actorType: o = "user", className: s = "", fallback: c = "—", withSecondary: l = !1 }) {
|
|
7
|
+
let { apiGatewayUrl: u, authToken: d, workspaceId: f } = e(), p = i(t(() => a ? [{
|
|
8
|
+
actorId: a,
|
|
9
|
+
actorType: o
|
|
10
|
+
}] : [], [a, o]), {
|
|
11
|
+
apiGatewayUrl: u,
|
|
12
|
+
authToken: d,
|
|
13
|
+
workspaceId: f
|
|
14
|
+
});
|
|
15
|
+
if (!a) return /* @__PURE__ */ n("span", {
|
|
16
|
+
className: s,
|
|
17
|
+
children: c
|
|
18
|
+
});
|
|
19
|
+
let m = p[a], h = m?.displayName ?? a;
|
|
20
|
+
return l && m?.secondaryLabel && m.secondaryLabel !== h ? /* @__PURE__ */ r("span", {
|
|
21
|
+
className: s,
|
|
22
|
+
children: [/* @__PURE__ */ n("span", {
|
|
23
|
+
className: "text-text-primary",
|
|
24
|
+
children: h
|
|
25
|
+
}), /* @__PURE__ */ n("span", {
|
|
26
|
+
className: "block text-xs text-text-secondary",
|
|
27
|
+
children: m.secondaryLabel
|
|
28
|
+
})]
|
|
29
|
+
}) : /* @__PURE__ */ n("span", {
|
|
30
|
+
className: s,
|
|
31
|
+
title: m?.email ?? a,
|
|
32
|
+
children: h
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { a as ActorName };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=ActorName.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActorName.js","names":[],"sources":["../../../src/brainyrich/components/ActorName.tsx"],"sourcesContent":["/**\n * Render an actor UUID (owner / advisor / proposedBy / acknowledgedBy …) as a\n * human-readable display name.\n * @module brainyrich/components/ActorName\n *\n * Wraps fe-libs `useActorProfiles`, which resolves identities via the GLOBAL\n * gateway (workspace-member → org-member → admin lookup → local fallback) with\n * an internal 5-minute cache. The MFE is handed a *workspace* `apiGatewayUrl`;\n * `useActorProfiles` detects that and only uses it for the workspace-scoped\n * tier, resolving the global gateway from the hostname itself — so forwarding\n * our workspace URL is safe. Network failures degrade to a short-id fallback,\n * never an error.\n */\n\nimport { useMemo } from 'react';\nimport { useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { useBrainyRich } from '../providers/BrainyRichProvider';\n\nexport interface ActorNameProps {\n /** Actor id (usually a user UUID). */\n id?: string | null;\n /** Actor type — 'user' (default), 'app', 'service', 'super_admin'. */\n actorType?: string;\n className?: string;\n /** Shown when `id` is absent/empty (default \"—\"). */\n fallback?: string;\n /** Also render the secondary label (email / short id) under the name. */\n withSecondary?: boolean;\n}\n\nexport function ActorName({\n id,\n actorType = 'user',\n className = '',\n fallback = '—',\n withSecondary = false,\n}: ActorNameProps) {\n const { apiGatewayUrl, authToken, workspaceId } = useBrainyRich();\n\n // Stable single-actor input list. Hook is always called (never conditional).\n const actors = useMemo<ActorInput[]>(\n () => (id ? [{ actorId: id, actorType }] : []),\n [id, actorType]\n );\n const profiles = useActorProfiles(actors, { apiGatewayUrl, authToken, workspaceId });\n\n if (!id) return <span className={className}>{fallback}</span>;\n\n const profile = profiles[id];\n const displayName = profile?.displayName ?? id;\n\n if (withSecondary && profile?.secondaryLabel && profile.secondaryLabel !== displayName) {\n return (\n <span className={className}>\n <span className=\"text-text-primary\">{displayName}</span>\n <span className=\"block text-xs text-text-secondary\">{profile.secondaryLabel}</span>\n </span>\n );\n }\n\n return (\n <span className={className} title={profile?.email ?? id}>\n {displayName}\n </span>\n );\n}\n"],"mappings":";;;;;AA8BA,SAAgB,EAAU,EACxB,OACA,eAAY,QACZ,eAAY,IACZ,cAAW,KACX,mBAAgB,MACC;CACjB,IAAM,EAAE,kBAAe,cAAW,mBAAgB,GAAe,EAO3D,IAAW,EAJF,QACN,IAAK,CAAC;EAAE,SAAS;EAAI;EAAW,CAAC,GAAG,EAAE,EAC7C,CAAC,GAAI,EAAU,CAChB,EACyC;EAAE;EAAe;EAAW;EAAa,CAAC;AAEpF,KAAI,CAAC,EAAI,QAAO,kBAAC,QAAD;EAAiB;YAAY;EAAgB,CAAA;CAE7D,IAAM,IAAU,EAAS,IACnB,IAAc,GAAS,eAAe;AAW5C,QATI,KAAiB,GAAS,kBAAkB,EAAQ,mBAAmB,IAEvE,kBAAC,QAAD;EAAiB;YAAjB,CACE,kBAAC,QAAD;GAAM,WAAU;aAAqB;GAAmB,CAAA,EACxD,kBAAC,QAAD;GAAM,WAAU;aAAqC,EAAQ;GAAsB,CAAA,CAC9E;MAKT,kBAAC,QAAD;EAAiB;EAAW,OAAO,GAAS,SAAS;YAClD;EACI,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
+
import { Button as n, ResponsiveDialog as r, ResponsiveDialogContent as i, ResponsiveDialogDescription as a, ResponsiveDialogFooter as o, ResponsiveDialogHeader as s, ResponsiveDialogTitle as c } from "@burdenoff/fe-libs/ui";
|
|
3
|
+
//#region src/brainyrich/components/ConfirmDialog.tsx
|
|
4
|
+
function l({ open: l, onOpenChange: u, title: d, description: f, confirmLabel: p = "Confirm", pendingLabel: m = "Working…", cancelLabel: h = "Cancel", destructive: g = !1, pending: _ = !1, onConfirm: v }) {
|
|
5
|
+
return /* @__PURE__ */ e(r, {
|
|
6
|
+
open: l,
|
|
7
|
+
onOpenChange: (e) => !_ && u(e),
|
|
8
|
+
children: /* @__PURE__ */ t(i, { children: [/* @__PURE__ */ t(s, { children: [/* @__PURE__ */ e(c, { children: d }), f ? /* @__PURE__ */ e(a, { children: f }) : null] }), /* @__PURE__ */ t(o, { children: [/* @__PURE__ */ e(n, {
|
|
9
|
+
variant: "outline",
|
|
10
|
+
onClick: () => u(!1),
|
|
11
|
+
disabled: _,
|
|
12
|
+
children: h
|
|
13
|
+
}), /* @__PURE__ */ e(n, {
|
|
14
|
+
variant: g ? "destructive" : "default",
|
|
15
|
+
onClick: v,
|
|
16
|
+
disabled: _,
|
|
17
|
+
"aria-busy": _,
|
|
18
|
+
children: _ ? m : p
|
|
19
|
+
})] })] })
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { l as ConfirmDialog };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=ConfirmDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../src/brainyrich/components/ConfirmDialog.tsx"],"sourcesContent":["/**\n * Shared confirmation dialog for destructive / irreversible actions.\n * @module brainyrich/components/ConfirmDialog\n *\n * Modeled on the archive-confirm dialog previously inlined in AccountsPage so\n * every destructive action (archive account/portfolio/goal/household, delete\n * holding, unlink funding, dismiss insight, reject rebalance) shares one\n * accessible, pending-aware surface.\n */\n\nimport type { ReactNode } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nexport interface ConfirmDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: string;\n description?: ReactNode;\n /** Label for the confirm button (default \"Confirm\"). */\n confirmLabel?: string;\n /** Label shown while `pending` (default \"Working…\"). */\n pendingLabel?: string;\n cancelLabel?: string;\n /** Render the confirm button with the destructive variant. */\n destructive?: boolean;\n /** Confirm action in flight — disables buttons + swaps the label. */\n pending?: boolean;\n onConfirm: () => void;\n}\n\nexport function ConfirmDialog({\n open,\n onOpenChange,\n title,\n description,\n confirmLabel = 'Confirm',\n pendingLabel = 'Working…',\n cancelLabel = 'Cancel',\n destructive = false,\n pending = false,\n onConfirm,\n}: ConfirmDialogProps) {\n return (\n <ResponsiveDialog open={open} onOpenChange={(next) => !pending && onOpenChange(next)}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n {description ? (\n <ResponsiveDialogDescription>{description}</ResponsiveDialogDescription>\n ) : null}\n </ResponsiveDialogHeader>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={pending}>\n {cancelLabel}\n </Button>\n <Button\n variant={destructive ? 'destructive' : 'default'}\n onClick={onConfirm}\n disabled={pending}\n aria-busy={pending}\n >\n {pending ? pendingLabel : confirmLabel}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;AAsCA,SAAgB,EAAc,EAC5B,SACA,iBACA,UACA,gBACA,kBAAe,WACf,kBAAe,YACf,iBAAc,UACd,iBAAc,IACd,aAAU,IACV,gBACqB;AACrB,QACE,kBAAC,GAAD;EAAwB;EAAM,eAAe,MAAS,CAAC,KAAW,EAAa,EAAK;YAClF,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAwB,GAA8B,CAAA,EACrD,IACC,kBAAC,GAAD,EAAA,UAA8B,GAA0C,CAAA,GACtE,KACmB,EAAA,CAAA,EACzB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,eAAe,EAAa,GAAM;GAAE,UAAU;aACrE;GACM,CAAA,EACT,kBAAC,GAAD;GACE,SAAS,IAAc,gBAAgB;GACvC,SAAS;GACT,UAAU;GACV,aAAW;aAEV,IAAU,IAAe;GACnB,CAAA,CACc,EAAA,CAAA,CACD,EAAA,CAAA;EACT,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Loader2 as e } from "lucide-react";
|
|
2
|
+
import { Fragment as t, jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
3
|
+
import { Button as i } from "@burdenoff/fe-libs/ui";
|
|
4
|
+
//#region src/brainyrich/components/LoadMore.tsx
|
|
5
|
+
function a({ hasMore: a, onClick: o, loading: s = !1, label: c = "Load more", loadingLabel: l = "Loading…", className: u = "" }) {
|
|
6
|
+
return a ? /* @__PURE__ */ n("div", {
|
|
7
|
+
className: `flex justify-center pt-4 ${u}`,
|
|
8
|
+
children: /* @__PURE__ */ n(i, {
|
|
9
|
+
variant: "outline",
|
|
10
|
+
size: "sm",
|
|
11
|
+
onClick: o,
|
|
12
|
+
disabled: s,
|
|
13
|
+
"aria-busy": s,
|
|
14
|
+
children: s ? /* @__PURE__ */ r(t, { children: [/* @__PURE__ */ n(e, {
|
|
15
|
+
className: "size-4 motion-safe:animate-spin",
|
|
16
|
+
"aria-hidden": "true"
|
|
17
|
+
}), l] }) : c
|
|
18
|
+
})
|
|
19
|
+
}) : null;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { a as LoadMore };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=LoadMore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadMore.js","names":[],"sources":["../../../src/brainyrich/components/LoadMore.tsx"],"sourcesContent":["/**\n * \"Load more\" button for cursor-paginated list pages.\n * @module brainyrich/components/LoadMore\n *\n * Pair with the `usePaged*` hooks in `../hooks/useBrainyRichApi` (they expose\n * `hasMore`, `loadMore`, `isFetchingNextPage`). Renders nothing when there is\n * no next page, so it can be dropped unconditionally at the end of a list.\n */\n\nimport { Loader2 } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\n\nexport interface LoadMoreProps {\n /** Whether another page exists. Component renders null when false. */\n hasMore: boolean;\n /** Fetch the next page. */\n onClick: () => void;\n /** Next-page fetch in flight. */\n loading?: boolean;\n label?: string;\n loadingLabel?: string;\n className?: string;\n}\n\nexport function LoadMore({\n hasMore,\n onClick,\n loading = false,\n label = 'Load more',\n loadingLabel = 'Loading…',\n className = '',\n}: LoadMoreProps) {\n if (!hasMore) return null;\n return (\n <div className={`flex justify-center pt-4 ${className}`}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={onClick}\n disabled={loading}\n aria-busy={loading}\n >\n {loading ? (\n <>\n <Loader2 className=\"size-4 motion-safe:animate-spin\" aria-hidden=\"true\" />\n {loadingLabel}\n </>\n ) : (\n label\n )}\n </Button>\n </div>\n );\n}\n"],"mappings":";;;;AAwBA,SAAgB,EAAS,EACvB,YACA,YACA,aAAU,IACV,WAAQ,aACR,kBAAe,YACf,eAAY,MACI;AAEhB,QADK,IAEH,kBAAC,OAAD;EAAK,WAAW,4BAA4B;YAC1C,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACI;GACT,UAAU;GACV,aAAW;aAEV,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;IAAS,WAAU;IAAkC,eAAY;IAAS,CAAA,EACzE,EACA,EAAA,CAAA,GAEH;GAEK,CAAA;EACL,CAAA,GAnBa"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toast as e } from "../../node_modules/sonner/dist/index.js";
|
|
2
|
+
import { extractBackendError as t } from "@burdenoff/fe-libs/shared/utils";
|
|
3
|
+
//#region src/brainyrich/components/notify.ts
|
|
4
|
+
var n = {
|
|
5
|
+
success(t, n) {
|
|
6
|
+
e.success(t, {
|
|
7
|
+
description: n,
|
|
8
|
+
duration: 4e3
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
info(t, n) {
|
|
12
|
+
e.info(t, {
|
|
13
|
+
description: n,
|
|
14
|
+
duration: 4e3
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
warning(t, n) {
|
|
18
|
+
e.warning(t, {
|
|
19
|
+
description: n,
|
|
20
|
+
duration: 8e3
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
error(t, n) {
|
|
24
|
+
e.error(t, {
|
|
25
|
+
description: n,
|
|
26
|
+
duration: Infinity,
|
|
27
|
+
closeButton: !0
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function r(e, n = "Something went wrong. Please try again.") {
|
|
32
|
+
let r = t(e);
|
|
33
|
+
return r?.message ? r.message : e instanceof Error && e.message.trim() ? e.message : n;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { r as errorMessage, n as notify };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","names":[],"sources":["../../../src/brainyrich/components/notify.ts"],"sourcesContent":["/**\n * Centralized user-feedback surface for the BrainyRich MFE.\n * @module brainyrich/components/notify\n *\n * WHY sonner `toast()` and NOT a scoped `<Toaster/>`:\n * sonner exposes a *module-level singleton* store — every `toast()` call\n * renders through whichever `<Toaster/>` is mounted. The host application\n * shell (`AppShellProvider` in `@burdenoff/fe-libs`) already mounts exactly\n * one `<Toaster/>` (see `AppShellProvider.tsx`), so calling `toast()` from\n * here renders through it. Mounting a SECOND `<Toaster/>` in BrainyRichRoot\n * would produce the documented double-toast regression, so we deliberately\n * do not. `sonner` is pinned to the same `^2.0.7` fe-libs uses so the bundler\n * dedupes to a single instance (a duplicated copy would break the singleton).\n *\n * Every page/component and every mutation hook emits feedback through these\n * helpers instead of hand-rolling markup. Durations follow the platform\n * notification spec: success/info auto-dismiss; errors stay until dismissed\n * (sonner renders a close button + announces via aria-live for WCAG AA).\n */\n\nimport { toast } from 'sonner';\nimport { extractBackendError } from '@burdenoff/fe-libs/shared/utils';\n\nexport const notify = {\n success(message: string, description?: string): void {\n toast.success(message, { description, duration: 4000 });\n },\n info(message: string, description?: string): void {\n toast.info(message, { description, duration: 4000 });\n },\n warning(message: string, description?: string): void {\n toast.warning(message, { description, duration: 8000 });\n },\n error(message: string, description?: string): void {\n toast.error(message, { description, duration: Infinity, closeButton: true });\n },\n};\n\n/**\n * Resolve a human-friendly message from any thrown value. The bra transport\n * throws `new Error(<graphql error message>)`, so this runs that message through\n * fe-libs' `extractBackendError` (which maps known codes to friendly copy and\n * hides technical/prisma strings) and falls back to the raw message.\n */\nexport function errorMessage(\n error: unknown,\n fallback = 'Something went wrong. Please try again.'\n): string {\n const extracted = extractBackendError(error);\n if (extracted?.message) return extracted.message;\n if (error instanceof Error && error.message.trim()) return error.message;\n return fallback;\n}\n"],"mappings":";;;AAuBA,IAAa,IAAS;CACpB,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,KAAK,GAAiB,GAA4B;AAChD,IAAM,KAAK,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEtD,QAAQ,GAAiB,GAA4B;AACnD,IAAM,QAAQ,GAAS;GAAE;GAAa,UAAU;GAAM,CAAC;;CAEzD,MAAM,GAAiB,GAA4B;AACjD,IAAM,MAAM,GAAS;GAAE;GAAa,UAAU;GAAU,aAAa;GAAM,CAAC;;CAE/E;AAQD,SAAgB,EACd,GACA,IAAW,2CACH;CACR,IAAM,IAAY,EAAoB,EAAM;AAG5C,QAFI,GAAW,UAAgB,EAAU,UACrC,aAAiB,SAAS,EAAM,QAAQ,MAAM,GAAS,EAAM,UAC1D"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { useBrainyRich as e } from "../providers/BrainyRichProvider.js";
|
|
2
|
+
import { useAccounts as t, useGoals as n, useHouseholds as r, usePortfolios as i } from "../hooks/useBrainyRichApi.js";
|
|
3
|
+
import { joinPath as a } from "../utils/navigation.js";
|
|
4
|
+
import { Loader2 as o } from "lucide-react";
|
|
5
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
6
|
+
import { Button as l, Label as u, Select as d, SelectContent as f, SelectGroup as p, SelectItem as m, SelectLabel as h, SelectTrigger as g, SelectValue as _ } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/brainyrich/components/pickers.tsx
|
|
8
|
+
var v = [
|
|
9
|
+
"ACCOUNT",
|
|
10
|
+
"PORTFOLIO",
|
|
11
|
+
"GOAL",
|
|
12
|
+
"HOUSEHOLD"
|
|
13
|
+
], y = {
|
|
14
|
+
ACCOUNT: "Account",
|
|
15
|
+
PORTFOLIO: "Portfolio",
|
|
16
|
+
GOAL: "Goal",
|
|
17
|
+
HOUSEHOLD: "Household"
|
|
18
|
+
};
|
|
19
|
+
function b(e, t) {
|
|
20
|
+
return `${e}:${t}`;
|
|
21
|
+
}
|
|
22
|
+
function x(e) {
|
|
23
|
+
if (!e) return null;
|
|
24
|
+
let t = e.indexOf(":");
|
|
25
|
+
if (t < 0) return null;
|
|
26
|
+
let n = e.slice(0, t), r = e.slice(t + 1);
|
|
27
|
+
return !v.includes(n) || !r ? null : {
|
|
28
|
+
subjectType: n,
|
|
29
|
+
subjectId: r
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function S({ message: t, cta: n, relativePath: r }) {
|
|
33
|
+
let { basePath: i = "/", navigate: o } = e(), u = a(i, r);
|
|
34
|
+
return /* @__PURE__ */ c("div", {
|
|
35
|
+
className: "rounded-lg border border-dashed border-border-subtle bg-bg-surface px-4 py-3 text-sm",
|
|
36
|
+
children: [/* @__PURE__ */ s("p", {
|
|
37
|
+
className: "text-text-secondary",
|
|
38
|
+
children: t
|
|
39
|
+
}), o ? /* @__PURE__ */ s(l, {
|
|
40
|
+
variant: "outline",
|
|
41
|
+
size: "sm",
|
|
42
|
+
className: "mt-2",
|
|
43
|
+
onClick: () => o(u),
|
|
44
|
+
children: n
|
|
45
|
+
}) : null]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function C({ id: e, label: t }) {
|
|
49
|
+
return /* @__PURE__ */ c("div", {
|
|
50
|
+
className: "space-y-2",
|
|
51
|
+
children: [t ? /* @__PURE__ */ s(u, {
|
|
52
|
+
htmlFor: e,
|
|
53
|
+
children: t
|
|
54
|
+
}) : null, /* @__PURE__ */ c("div", {
|
|
55
|
+
className: "flex h-9 items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-secondary",
|
|
56
|
+
"aria-busy": "true",
|
|
57
|
+
children: [/* @__PURE__ */ s(o, {
|
|
58
|
+
className: "size-4 motion-safe:animate-spin",
|
|
59
|
+
"aria-hidden": "true"
|
|
60
|
+
}), "Loading…"]
|
|
61
|
+
})]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function w({ value: e, onChange: a, id: o = "subject-picker", label: l = "Subject", placeholder: v = "Select a subject", disabled: x = !1 }) {
|
|
65
|
+
let w = t(), T = i(), E = n(), D = r(), O = w.isLoading || T.isLoading || E.isLoading || D.isLoading, k = w.data.length + T.data.length + E.data.length + D.data.length;
|
|
66
|
+
if (O) return /* @__PURE__ */ s(C, {
|
|
67
|
+
id: o,
|
|
68
|
+
label: l
|
|
69
|
+
});
|
|
70
|
+
if (k === 0) return /* @__PURE__ */ c("div", {
|
|
71
|
+
className: "space-y-2",
|
|
72
|
+
children: [l ? /* @__PURE__ */ s(u, {
|
|
73
|
+
htmlFor: o,
|
|
74
|
+
children: l
|
|
75
|
+
}) : null, /* @__PURE__ */ s(S, {
|
|
76
|
+
message: "No accounts, portfolios, goals or households yet — create one to attach this insight.",
|
|
77
|
+
cta: "Create an account",
|
|
78
|
+
relativePath: "accounts"
|
|
79
|
+
})]
|
|
80
|
+
});
|
|
81
|
+
let A = [
|
|
82
|
+
{
|
|
83
|
+
kind: "ACCOUNT",
|
|
84
|
+
items: w.data.map((e) => ({
|
|
85
|
+
id: e.id,
|
|
86
|
+
name: e.name
|
|
87
|
+
}))
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: "PORTFOLIO",
|
|
91
|
+
items: T.data.map((e) => ({
|
|
92
|
+
id: e.id,
|
|
93
|
+
name: e.name
|
|
94
|
+
}))
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
kind: "GOAL",
|
|
98
|
+
items: E.data.map((e) => ({
|
|
99
|
+
id: e.id,
|
|
100
|
+
name: e.name
|
|
101
|
+
}))
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
kind: "HOUSEHOLD",
|
|
105
|
+
items: D.data.map((e) => ({
|
|
106
|
+
id: e.id,
|
|
107
|
+
name: e.name
|
|
108
|
+
}))
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
return /* @__PURE__ */ c("div", {
|
|
112
|
+
className: "space-y-2",
|
|
113
|
+
children: [l ? /* @__PURE__ */ s(u, {
|
|
114
|
+
htmlFor: o,
|
|
115
|
+
children: l
|
|
116
|
+
}) : null, /* @__PURE__ */ c(d, {
|
|
117
|
+
value: e || void 0,
|
|
118
|
+
onValueChange: a,
|
|
119
|
+
disabled: x,
|
|
120
|
+
children: [/* @__PURE__ */ s(g, {
|
|
121
|
+
id: o,
|
|
122
|
+
"aria-label": l,
|
|
123
|
+
children: /* @__PURE__ */ s(_, { placeholder: v })
|
|
124
|
+
}), /* @__PURE__ */ s(f, { children: A.filter((e) => e.items.length > 0).map((e) => /* @__PURE__ */ c(p, { children: [/* @__PURE__ */ s(h, { children: y[e.kind] }), e.items.map((t) => /* @__PURE__ */ s(m, {
|
|
125
|
+
value: b(e.kind, t.id),
|
|
126
|
+
children: t.name
|
|
127
|
+
}, `${e.kind}:${t.id}`))] }, e.kind)) })]
|
|
128
|
+
})]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
var T = {
|
|
132
|
+
account: {
|
|
133
|
+
label: "Account",
|
|
134
|
+
placeholder: "Select an account",
|
|
135
|
+
relativePath: "accounts",
|
|
136
|
+
cta: "Create an account",
|
|
137
|
+
emptyMessage: "No accounts yet — create one first."
|
|
138
|
+
},
|
|
139
|
+
household: {
|
|
140
|
+
label: "Household",
|
|
141
|
+
placeholder: "Select a household",
|
|
142
|
+
relativePath: "family",
|
|
143
|
+
cta: "Create a household",
|
|
144
|
+
emptyMessage: "No households yet — create one first."
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
function E({ kind: e, value: n, onChange: i, id: a, label: o, placeholder: l, disabled: p = !1 }) {
|
|
148
|
+
let h = T[e], v = t(), y = r(), b = e === "account" ? v : y, x = a ?? `${e}-picker`, w = o ?? h.label;
|
|
149
|
+
if (b.isLoading) return /* @__PURE__ */ s(C, {
|
|
150
|
+
id: x,
|
|
151
|
+
label: w
|
|
152
|
+
});
|
|
153
|
+
let E = b.data.map((e) => ({
|
|
154
|
+
id: e.id,
|
|
155
|
+
name: e.name
|
|
156
|
+
}));
|
|
157
|
+
return E.length === 0 ? /* @__PURE__ */ c("div", {
|
|
158
|
+
className: "space-y-2",
|
|
159
|
+
children: [w ? /* @__PURE__ */ s(u, {
|
|
160
|
+
htmlFor: x,
|
|
161
|
+
children: w
|
|
162
|
+
}) : null, /* @__PURE__ */ s(S, {
|
|
163
|
+
message: h.emptyMessage,
|
|
164
|
+
cta: h.cta,
|
|
165
|
+
relativePath: h.relativePath
|
|
166
|
+
})]
|
|
167
|
+
}) : /* @__PURE__ */ c("div", {
|
|
168
|
+
className: "space-y-2",
|
|
169
|
+
children: [w ? /* @__PURE__ */ s(u, {
|
|
170
|
+
htmlFor: x,
|
|
171
|
+
children: w
|
|
172
|
+
}) : null, /* @__PURE__ */ c(d, {
|
|
173
|
+
value: n || void 0,
|
|
174
|
+
onValueChange: i,
|
|
175
|
+
disabled: p,
|
|
176
|
+
children: [/* @__PURE__ */ s(g, {
|
|
177
|
+
id: x,
|
|
178
|
+
"aria-label": w,
|
|
179
|
+
children: /* @__PURE__ */ s(_, { placeholder: l ?? h.placeholder })
|
|
180
|
+
}), /* @__PURE__ */ s(f, { children: E.map((e) => /* @__PURE__ */ s(m, {
|
|
181
|
+
value: e.id,
|
|
182
|
+
children: e.name
|
|
183
|
+
}, e.id)) })]
|
|
184
|
+
})]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function D(e) {
|
|
188
|
+
return /* @__PURE__ */ s(E, {
|
|
189
|
+
kind: "account",
|
|
190
|
+
...e
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
var O = "__none__";
|
|
194
|
+
function k({ value: e, onChange: t, id: n = "household-picker", label: i = "Household", noneLabel: a = "No household", disabled: o = !1 }) {
|
|
195
|
+
let l = r();
|
|
196
|
+
return l.isLoading ? /* @__PURE__ */ s(C, {
|
|
197
|
+
id: n,
|
|
198
|
+
label: i
|
|
199
|
+
}) : /* @__PURE__ */ c("div", {
|
|
200
|
+
className: "space-y-2",
|
|
201
|
+
children: [
|
|
202
|
+
i ? /* @__PURE__ */ s(u, {
|
|
203
|
+
htmlFor: n,
|
|
204
|
+
children: i
|
|
205
|
+
}) : null,
|
|
206
|
+
/* @__PURE__ */ c(d, {
|
|
207
|
+
value: e ?? O,
|
|
208
|
+
onValueChange: (e) => t(e === O ? null : e),
|
|
209
|
+
disabled: o,
|
|
210
|
+
children: [/* @__PURE__ */ s(g, {
|
|
211
|
+
id: n,
|
|
212
|
+
"aria-label": i,
|
|
213
|
+
children: /* @__PURE__ */ s(_, {})
|
|
214
|
+
}), /* @__PURE__ */ c(f, { children: [/* @__PURE__ */ s(m, {
|
|
215
|
+
value: O,
|
|
216
|
+
children: a
|
|
217
|
+
}), l.data.map((e) => /* @__PURE__ */ s(m, {
|
|
218
|
+
value: e.id,
|
|
219
|
+
children: e.name
|
|
220
|
+
}, e.id))] })]
|
|
221
|
+
}),
|
|
222
|
+
l.data.length === 0 ? /* @__PURE__ */ s("p", {
|
|
223
|
+
className: "text-xs text-text-secondary",
|
|
224
|
+
children: "No households yet — you can create one in Households and link this account later."
|
|
225
|
+
}) : null
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
export { D as AccountPicker, E as EntityPicker, k as HouseholdPicker, v as SUBJECT_KINDS, w as SubjectPicker, b as encodeSubject, x as parseSubject };
|
|
231
|
+
|
|
232
|
+
//# sourceMappingURL=pickers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pickers.js","names":[],"sources":["../../../src/brainyrich/components/pickers.tsx"],"sourcesContent":["/**\n * Reusable entity / subject pickers.\n * @module brainyrich/components/pickers\n *\n * - `SubjectPicker` — encodes `SUBJECT_TYPE:id` across accounts, portfolios,\n * goals and households (for the AiAssistant insight subject).\n * - `EntityPicker` — generic account / household Select returning a bare id.\n * - `AccountPicker` — convenience wrapper (kind=\"account\").\n * - `HouseholdPicker` — OPTIONAL household Select with a \"None\" option, for the\n * account create/edit form (accounts can be linked to a\n * household). Never a dead-end when no households exist.\n *\n * When a *required* source list is empty every picker renders a CTA that routes\n * the user to create the missing entity instead of showing an empty dropdown.\n */\n\nimport { Loader2 } from 'lucide-react';\nimport {\n Button,\n Label,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useAccounts, useGoals, useHouseholds, usePortfolios } from '../hooks/useBrainyRichApi';\nimport { useBrainyRich } from '../providers/BrainyRichProvider';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Subject encoding (matches the SDL `subjectType` convention — uppercase)\n// ============================================================================\n\nexport const SUBJECT_KINDS = ['ACCOUNT', 'PORTFOLIO', 'GOAL', 'HOUSEHOLD'] as const;\nexport type SubjectKind = (typeof SUBJECT_KINDS)[number];\n\nconst SUBJECT_KIND_LABEL: Record<SubjectKind, string> = {\n ACCOUNT: 'Account',\n PORTFOLIO: 'Portfolio',\n GOAL: 'Goal',\n HOUSEHOLD: 'Household',\n};\n\n/** Encode a subject selection as the `SUBJECT_TYPE:id` token a SubjectPicker emits. */\nexport function encodeSubject(kind: SubjectKind, id: string): string {\n return `${kind}:${id}`;\n}\n\n/** Decode a `SUBJECT_TYPE:id` token back into `{ subjectType, subjectId }`. */\nexport function parseSubject(\n value: string | null | undefined\n): { subjectType: SubjectKind; subjectId: string } | null {\n if (!value) return null;\n const idx = value.indexOf(':');\n if (idx < 0) return null;\n const kind = value.slice(0, idx) as SubjectKind;\n const id = value.slice(idx + 1);\n if (!SUBJECT_KINDS.includes(kind) || !id) return null;\n return { subjectType: kind, subjectId: id };\n}\n\n// ============================================================================\n// Shared empty-state CTA (routes to the create surface, never a dead-end)\n// ============================================================================\n\nfunction PickerCTA({ message, cta, relativePath }: { message: string; cta: string; relativePath: string }) {\n const { basePath = '/', navigate } = useBrainyRich();\n const target = joinPath(basePath, relativePath);\n return (\n <div className=\"rounded-lg border border-dashed border-border-subtle bg-bg-surface px-4 py-3 text-sm\">\n <p className=\"text-text-secondary\">{message}</p>\n {navigate ? (\n <Button variant=\"outline\" size=\"sm\" className=\"mt-2\" onClick={() => navigate(target)}>\n {cta}\n </Button>\n ) : null}\n </div>\n );\n}\n\nfunction PickerLoading({ id, label }: { id?: string; label?: string }) {\n return (\n <div className=\"space-y-2\">\n {label ? <Label htmlFor={id}>{label}</Label> : null}\n <div\n className=\"flex h-9 items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-secondary\"\n aria-busy=\"true\"\n >\n <Loader2 className=\"size-4 motion-safe:animate-spin\" aria-hidden=\"true\" />\n Loading…\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// SubjectPicker — accounts + portfolios + goals + households\n// ============================================================================\n\nexport interface SubjectPickerProps {\n /** Current `SUBJECT_TYPE:id` token, or empty. */\n value?: string;\n onChange: (value: string) => void;\n id?: string;\n label?: string;\n placeholder?: string;\n disabled?: boolean;\n}\n\nexport function SubjectPicker({\n value,\n onChange,\n id = 'subject-picker',\n label = 'Subject',\n placeholder = 'Select a subject',\n disabled = false,\n}: SubjectPickerProps) {\n const accounts = useAccounts();\n const portfolios = usePortfolios();\n const goals = useGoals();\n const households = useHouseholds();\n\n const loading =\n accounts.isLoading || portfolios.isLoading || goals.isLoading || households.isLoading;\n const total =\n accounts.data.length + portfolios.data.length + goals.data.length + households.data.length;\n\n if (loading) return <PickerLoading id={id} label={label} />;\n\n if (total === 0) {\n return (\n <div className=\"space-y-2\">\n {label ? <Label htmlFor={id}>{label}</Label> : null}\n <PickerCTA\n message=\"No accounts, portfolios, goals or households yet — create one to attach this insight.\"\n cta=\"Create an account\"\n relativePath=\"accounts\"\n />\n </div>\n );\n }\n\n const groups: Array<{ kind: SubjectKind; items: Array<{ id: string; name: string }> }> = [\n { kind: 'ACCOUNT', items: accounts.data.map((a) => ({ id: a.id, name: a.name })) },\n { kind: 'PORTFOLIO', items: portfolios.data.map((p) => ({ id: p.id, name: p.name })) },\n { kind: 'GOAL', items: goals.data.map((g) => ({ id: g.id, name: g.name })) },\n { kind: 'HOUSEHOLD', items: households.data.map((h) => ({ id: h.id, name: h.name })) },\n ];\n\n return (\n <div className=\"space-y-2\">\n {label ? <Label htmlFor={id}>{label}</Label> : null}\n <Select value={value || undefined} onValueChange={onChange} disabled={disabled}>\n <SelectTrigger id={id} aria-label={label}>\n <SelectValue placeholder={placeholder} />\n </SelectTrigger>\n <SelectContent>\n {groups\n .filter((group) => group.items.length > 0)\n .map((group) => (\n <SelectGroup key={group.kind}>\n <SelectLabel>{SUBJECT_KIND_LABEL[group.kind]}</SelectLabel>\n {group.items.map((item) => (\n <SelectItem key={`${group.kind}:${item.id}`} value={encodeSubject(group.kind, item.id)}>\n {item.name}\n </SelectItem>\n ))}\n </SelectGroup>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n\n// ============================================================================\n// EntityPicker — generic account / household id picker\n// ============================================================================\n\nexport interface EntityPickerProps {\n kind: 'account' | 'household';\n value?: string | null;\n onChange: (id: string) => void;\n id?: string;\n label?: string;\n placeholder?: string;\n disabled?: boolean;\n}\n\nconst ENTITY_META: Record<\n EntityPickerProps['kind'],\n { label: string; placeholder: string; relativePath: string; cta: string; emptyMessage: string }\n> = {\n account: {\n label: 'Account',\n placeholder: 'Select an account',\n relativePath: 'accounts',\n cta: 'Create an account',\n emptyMessage: 'No accounts yet — create one first.',\n },\n household: {\n label: 'Household',\n placeholder: 'Select a household',\n relativePath: 'family',\n cta: 'Create a household',\n emptyMessage: 'No households yet — create one first.',\n },\n};\n\nexport function EntityPicker({\n kind,\n value,\n onChange,\n id,\n label,\n placeholder,\n disabled = false,\n}: EntityPickerProps) {\n const meta = ENTITY_META[kind];\n const accounts = useAccounts();\n const households = useHouseholds();\n const source = kind === 'account' ? accounts : households;\n const fieldId = id ?? `${kind}-picker`;\n const fieldLabel = label ?? meta.label;\n\n if (source.isLoading) return <PickerLoading id={fieldId} label={fieldLabel} />;\n\n const options = source.data.map((entity) => ({ id: entity.id, name: entity.name }));\n\n if (options.length === 0) {\n return (\n <div className=\"space-y-2\">\n {fieldLabel ? <Label htmlFor={fieldId}>{fieldLabel}</Label> : null}\n <PickerCTA message={meta.emptyMessage} cta={meta.cta} relativePath={meta.relativePath} />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-2\">\n {fieldLabel ? <Label htmlFor={fieldId}>{fieldLabel}</Label> : null}\n <Select value={value || undefined} onValueChange={onChange} disabled={disabled}>\n <SelectTrigger id={fieldId} aria-label={fieldLabel}>\n <SelectValue placeholder={placeholder ?? meta.placeholder} />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.id} value={option.id}>\n {option.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n\n/** Convenience wrapper — a required account picker. */\nexport function AccountPicker(props: Omit<EntityPickerProps, 'kind'>) {\n return <EntityPicker kind=\"account\" {...props} />;\n}\n\n// ============================================================================\n// HouseholdPicker — OPTIONAL household link (account form, item 6)\n// ============================================================================\n\nconst HOUSEHOLD_NONE = '__none__';\n\nexport interface HouseholdPickerProps {\n /** Current householdId, or null/undefined for \"no household\". */\n value?: string | null;\n onChange: (householdId: string | null) => void;\n id?: string;\n label?: string;\n /** Label for the \"unlinked\" option (default \"No household\"). */\n noneLabel?: string;\n disabled?: boolean;\n}\n\n/**\n * OPTIONAL household selector. Always offers a \"No household\" option, so it is\n * never a dead-end even when zero households exist (it just shows a hint). Emits\n * `null` for the none option and a bare householdId otherwise.\n */\nexport function HouseholdPicker({\n value,\n onChange,\n id = 'household-picker',\n label = 'Household',\n noneLabel = 'No household',\n disabled = false,\n}: HouseholdPickerProps) {\n const households = useHouseholds();\n\n if (households.isLoading) return <PickerLoading id={id} label={label} />;\n\n return (\n <div className=\"space-y-2\">\n {label ? <Label htmlFor={id}>{label}</Label> : null}\n <Select\n value={value ?? HOUSEHOLD_NONE}\n onValueChange={(next) => onChange(next === HOUSEHOLD_NONE ? null : next)}\n disabled={disabled}\n >\n <SelectTrigger id={id} aria-label={label}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={HOUSEHOLD_NONE}>{noneLabel}</SelectItem>\n {households.data.map((household) => (\n <SelectItem key={household.id} value={household.id}>\n {household.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {households.data.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No households yet — you can create one in Households and link this account later.\n </p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|