@burdenoff/microfe-brainyrich 2026.1025.2 → 2026.1028.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brainyrich/components/AggregationPanel.d.ts.map +1 -1
- package/dist/brainyrich/components/AggregationPanel.js +130 -88
- package/dist/brainyrich/components/AggregationPanel.js.map +1 -1
- package/dist/brainyrich/hooks/useAggregation.d.ts +8 -0
- package/dist/brainyrich/hooks/useAggregation.d.ts.map +1 -1
- package/dist/brainyrich/hooks/useAggregation.js +32 -10
- package/dist/brainyrich/hooks/useAggregation.js.map +1 -1
- package/dist/brainyrich/hooks/useBrainyRichApi.d.ts +10 -1
- package/dist/brainyrich/hooks/useBrainyRichApi.d.ts.map +1 -1
- package/dist/brainyrich/hooks/useBrainyRichApi.js +66 -50
- package/dist/brainyrich/hooks/useBrainyRichApi.js.map +1 -1
- package/dist/brainyrich/pages/FirmPage.d.ts.map +1 -1
- package/dist/brainyrich/pages/FirmPage.js +143 -128
- package/dist/brainyrich/pages/FirmPage.js.map +1 -1
- package/dist/brainyrich/types/bra.d.ts +10 -0
- package/dist/brainyrich/types/bra.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,287 +1,302 @@
|
|
|
1
1
|
import { tWithFallback as e } from "../utils/i18n.js";
|
|
2
|
-
import { useAdvisors as t, useEnsureAdvisorProfile as n, useFirmReport as r, useMyAdvisorProfile as i, useSetAdvisorRole as a } from "../hooks/useBrainyRichApi.js";
|
|
3
|
-
import { FadeIn as
|
|
4
|
-
import { AllocationBars as
|
|
5
|
-
import { useState as
|
|
6
|
-
import { Building2 as
|
|
7
|
-
import { useI18n as
|
|
8
|
-
import { jsx as
|
|
9
|
-
import { Badge as
|
|
2
|
+
import { useAdvisors as t, useEnsureAdvisorProfile as n, useFirmReport as r, useMyAdvisorProfile as i, useSetAdvisorRole as a, useSetAdvisorStatus as o } from "../hooks/useBrainyRichApi.js";
|
|
3
|
+
import { FadeIn as s, StatTile as c } from "../components/viz.js";
|
|
4
|
+
import { AllocationBars as l, EmptyState as u, ErrorState as d, LoadingState as f, SectionShell as p, formatDate as m, formatMoney as h, formatPct as g } from "./_shared.js";
|
|
5
|
+
import { useState as _ } from "react";
|
|
6
|
+
import { Building2 as v, PlusCircle as y, ShieldCheck as b, UserPlus as x, Users as S } from "lucide-react";
|
|
7
|
+
import { useI18n as C } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
8
|
+
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
9
|
+
import { Badge as E, Button as D, Card as O, CardContent as k, CardDescription as A, CardHeader as j, CardTitle as M, Input as N, Label as P, Select as F, SelectContent as I, SelectItem as L, SelectTrigger as R, SelectValue as z, Table as B, TableBody as V, TableCell as H, TableHead as U, TableHeader as W, TableRow as G } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/brainyrich/pages/FirmPage.tsx
|
|
11
|
-
var
|
|
12
|
-
function
|
|
13
|
-
return t <= 0 ? [] : e.slice(0,
|
|
11
|
+
var K = 8;
|
|
12
|
+
function q(e, t, n) {
|
|
13
|
+
return t <= 0 ? [] : e.slice(0, K).map((e) => {
|
|
14
14
|
let r = e.aum / t * 100;
|
|
15
15
|
return {
|
|
16
16
|
label: e.displayName ?? e.email ?? e.advisorId,
|
|
17
17
|
value: r,
|
|
18
18
|
tone: "accent",
|
|
19
|
-
caption: `${
|
|
19
|
+
caption: `${h(e.aum, n)} · ${g(r)}`
|
|
20
20
|
};
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
let { t } =
|
|
25
|
-
return a.isLoading ? /* @__PURE__ */
|
|
23
|
+
function J() {
|
|
24
|
+
let { t } = C(), r = (n, r, i) => e(t, n, r, i), a = i(), o = n(), c = a.data, l = c?.role === "FIRM_ADMIN", [d, m] = _(""), [h, g] = _("");
|
|
25
|
+
return a.isLoading ? /* @__PURE__ */ w(p, {
|
|
26
26
|
title: r("brainyrich.firm.title", "Firm"),
|
|
27
27
|
description: r("brainyrich.firm.description", "Firm roster and firm-wide AUM reporting."),
|
|
28
|
-
children: /* @__PURE__ */
|
|
29
|
-
}) : c ? /* @__PURE__ */
|
|
28
|
+
children: /* @__PURE__ */ w(f, {})
|
|
29
|
+
}) : c ? /* @__PURE__ */ T(p, {
|
|
30
30
|
title: r("brainyrich.firm.title", "Firm"),
|
|
31
31
|
description: r("brainyrich.firm.description", "Firm roster and firm-wide AUM reporting."),
|
|
32
|
-
children: [/* @__PURE__ */
|
|
32
|
+
children: [/* @__PURE__ */ w(s, { children: /* @__PURE__ */ w(O, { children: /* @__PURE__ */ w(k, {
|
|
33
33
|
className: "flex flex-wrap items-center justify-between gap-3 py-4",
|
|
34
|
-
children: /* @__PURE__ */
|
|
34
|
+
children: /* @__PURE__ */ T("div", {
|
|
35
35
|
className: "flex items-center gap-2",
|
|
36
36
|
children: [
|
|
37
|
-
/* @__PURE__ */
|
|
37
|
+
/* @__PURE__ */ w(b, {
|
|
38
38
|
className: "size-4 text-status-success-text",
|
|
39
39
|
"aria-hidden": "true"
|
|
40
40
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
41
|
+
/* @__PURE__ */ w("span", {
|
|
42
42
|
className: "text-sm text-text-secondary",
|
|
43
43
|
children: r("brainyrich.firm.you", "You are on the firm roster as")
|
|
44
44
|
}),
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
variant:
|
|
45
|
+
/* @__PURE__ */ w(E, {
|
|
46
|
+
variant: l ? "default" : "secondary",
|
|
47
47
|
children: c.role
|
|
48
48
|
})
|
|
49
49
|
]
|
|
50
50
|
})
|
|
51
|
-
}) }) }),
|
|
52
|
-
icon: /* @__PURE__ */
|
|
51
|
+
}) }) }), l ? /* @__PURE__ */ w(Y, { translate: r }) : /* @__PURE__ */ w(s, { children: /* @__PURE__ */ w(u, {
|
|
52
|
+
icon: /* @__PURE__ */ w(S, {
|
|
53
53
|
className: "size-8",
|
|
54
54
|
"aria-hidden": "true"
|
|
55
55
|
}),
|
|
56
56
|
title: r("brainyrich.firm.notAdmin", "Firm-wide roster and AUM reporting are visible to firm admins only.")
|
|
57
57
|
}) })]
|
|
58
|
-
}) : /* @__PURE__ */
|
|
58
|
+
}) : /* @__PURE__ */ w(p, {
|
|
59
59
|
title: r("brainyrich.firm.title", "Firm"),
|
|
60
60
|
description: r("brainyrich.firm.description", "Firm roster and firm-wide AUM reporting."),
|
|
61
|
-
children: /* @__PURE__ */
|
|
61
|
+
children: /* @__PURE__ */ w(s, { children: /* @__PURE__ */ T(O, { children: [/* @__PURE__ */ T(j, { children: [/* @__PURE__ */ T(M, {
|
|
62
62
|
className: "flex items-center gap-2",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
63
|
+
children: [/* @__PURE__ */ w(v, {
|
|
64
64
|
className: "size-4",
|
|
65
65
|
"aria-hidden": "true"
|
|
66
66
|
}), r("brainyrich.firm.join.title", "Join the firm roster")]
|
|
67
|
-
}), /* @__PURE__ */
|
|
67
|
+
}), /* @__PURE__ */ w(A, { children: r("brainyrich.firm.join.description", "You're not on this workspace's BrainyRich firm roster yet. The first advisor to register becomes the firm admin automatically; anyone after that is invited by an existing admin.") })] }), /* @__PURE__ */ T(k, {
|
|
68
68
|
className: "space-y-4",
|
|
69
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ T("div", {
|
|
70
70
|
className: "grid gap-4 sm:grid-cols-2",
|
|
71
|
-
children: [/* @__PURE__ */
|
|
71
|
+
children: [/* @__PURE__ */ T("div", {
|
|
72
72
|
className: "space-y-1.5",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ w(P, {
|
|
74
74
|
htmlFor: "firm-display-name",
|
|
75
75
|
children: r("brainyrich.firm.fields.displayName", "Display name")
|
|
76
|
-
}), /* @__PURE__ */
|
|
76
|
+
}), /* @__PURE__ */ w(N, {
|
|
77
77
|
id: "firm-display-name",
|
|
78
|
-
value:
|
|
78
|
+
value: d,
|
|
79
79
|
onChange: (e) => m(e.target.value)
|
|
80
80
|
})]
|
|
81
|
-
}), /* @__PURE__ */
|
|
81
|
+
}), /* @__PURE__ */ T("div", {
|
|
82
82
|
className: "space-y-1.5",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
83
|
+
children: [/* @__PURE__ */ w(P, {
|
|
84
84
|
htmlFor: "firm-email",
|
|
85
85
|
children: r("brainyrich.firm.fields.email", "Email")
|
|
86
|
-
}), /* @__PURE__ */
|
|
86
|
+
}), /* @__PURE__ */ w(N, {
|
|
87
87
|
id: "firm-email",
|
|
88
88
|
type: "email",
|
|
89
89
|
value: h,
|
|
90
|
-
onChange: (e) =>
|
|
90
|
+
onChange: (e) => g(e.target.value)
|
|
91
91
|
})]
|
|
92
92
|
})]
|
|
93
|
-
}), /* @__PURE__ */
|
|
94
|
-
disabled:
|
|
95
|
-
onClick: () =>
|
|
96
|
-
displayName:
|
|
93
|
+
}), /* @__PURE__ */ T(D, {
|
|
94
|
+
disabled: o.isPending,
|
|
95
|
+
onClick: () => o.mutate({
|
|
96
|
+
displayName: d || void 0,
|
|
97
97
|
email: h || void 0
|
|
98
98
|
}),
|
|
99
|
-
children: [/* @__PURE__ */
|
|
99
|
+
children: [/* @__PURE__ */ w(x, {
|
|
100
100
|
className: "size-4",
|
|
101
101
|
"aria-hidden": "true"
|
|
102
|
-
}),
|
|
102
|
+
}), o.isPending ? "Registering…" : "Register on firm roster"]
|
|
103
103
|
})]
|
|
104
104
|
})] }) })
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
function
|
|
108
|
-
let i = r(),
|
|
109
|
-
return /* @__PURE__ */
|
|
107
|
+
function Y({ translate: e }) {
|
|
108
|
+
let i = r(), p = t(), g = a(), b = o(), [x, C] = _(""), [J, Y] = _("ADVISOR"), X = n(), Z = i.data;
|
|
109
|
+
return /* @__PURE__ */ T("div", {
|
|
110
110
|
className: "space-y-6",
|
|
111
111
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ w(s, { children: /* @__PURE__ */ T("div", {
|
|
113
113
|
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
|
|
114
114
|
children: [
|
|
115
|
-
/* @__PURE__ */
|
|
115
|
+
/* @__PURE__ */ w(c, {
|
|
116
116
|
label: e("brainyrich.firm.kpis.totalAum", "Firm AUM"),
|
|
117
|
-
value:
|
|
117
|
+
value: Z ? h(Z.totalAum, Z.currency) : "—",
|
|
118
118
|
glow: !0
|
|
119
119
|
}),
|
|
120
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ w(c, {
|
|
121
121
|
label: e("brainyrich.firm.kpis.advisors", "Advisors"),
|
|
122
|
-
value:
|
|
122
|
+
value: Z ? String(Z.totalAdvisors) : "—"
|
|
123
123
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ w(c, {
|
|
125
125
|
label: e("brainyrich.firm.kpis.households", "Households"),
|
|
126
|
-
value:
|
|
126
|
+
value: Z ? String(Z.totalHouseholds) : "—"
|
|
127
127
|
}),
|
|
128
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ w(c, {
|
|
129
129
|
label: e("brainyrich.firm.kpis.asOf", "As of"),
|
|
130
|
-
value:
|
|
130
|
+
value: Z ? m(Z.asOf) : "—"
|
|
131
131
|
})
|
|
132
132
|
]
|
|
133
133
|
}) }),
|
|
134
|
-
/* @__PURE__ */
|
|
135
|
-
icon: /* @__PURE__ */
|
|
134
|
+
/* @__PURE__ */ T(O, { children: [/* @__PURE__ */ T(j, { children: [/* @__PURE__ */ w(M, { children: e("brainyrich.firm.byAdvisor.title", "AUM by advisor") }), /* @__PURE__ */ w(A, { children: e("brainyrich.firm.byAdvisor.description", "Sorted by AUM, largest book first — a real rollup of BraHousehold.aum per advisor.") })] }), /* @__PURE__ */ w(k, { children: i.isLoading ? /* @__PURE__ */ w(f, {}) : i.isError ? /* @__PURE__ */ w(d, { onRetry: () => i.refetch() }) : !Z || Z.byAdvisor.length === 0 ? /* @__PURE__ */ w(u, {
|
|
135
|
+
icon: /* @__PURE__ */ w(v, {
|
|
136
136
|
className: "size-8",
|
|
137
137
|
"aria-hidden": "true"
|
|
138
138
|
}),
|
|
139
139
|
title: e("brainyrich.firm.byAdvisor.empty", "No advisor books yet")
|
|
140
|
-
}) : /* @__PURE__ */
|
|
140
|
+
}) : /* @__PURE__ */ T("div", {
|
|
141
141
|
className: "space-y-6",
|
|
142
|
-
children: [
|
|
142
|
+
children: [q(Z.byAdvisor, Z.totalAum, Z.currency).length > 0 ? /* @__PURE__ */ T("div", {
|
|
143
143
|
className: "space-y-2",
|
|
144
144
|
children: [
|
|
145
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ w("p", {
|
|
146
146
|
className: "text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
147
147
|
children: e("brainyrich.firm.byAdvisor.shareLabel", "Share of firm AUM")
|
|
148
148
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
|
|
149
|
+
/* @__PURE__ */ w(l, { data: q(Z.byAdvisor, Z.totalAum, Z.currency) }),
|
|
150
|
+
Z.byAdvisor.length > K ? /* @__PURE__ */ T("p", {
|
|
151
151
|
className: "text-xs text-text-secondary",
|
|
152
152
|
children: [
|
|
153
153
|
"Showing the ",
|
|
154
|
-
|
|
154
|
+
K,
|
|
155
155
|
" largest books — every advisor is listed below."
|
|
156
156
|
]
|
|
157
157
|
}) : null
|
|
158
158
|
]
|
|
159
|
-
}) : null, /* @__PURE__ */
|
|
160
|
-
/* @__PURE__ */
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
-
/* @__PURE__ */
|
|
163
|
-
/* @__PURE__ */
|
|
159
|
+
}) : null, /* @__PURE__ */ T(B, { children: [/* @__PURE__ */ w(W, { children: /* @__PURE__ */ T(G, { children: [
|
|
160
|
+
/* @__PURE__ */ w(U, { children: "Advisor" }),
|
|
161
|
+
/* @__PURE__ */ w(U, { children: "Role" }),
|
|
162
|
+
/* @__PURE__ */ w(U, { children: "Households" }),
|
|
163
|
+
/* @__PURE__ */ w(U, {
|
|
164
164
|
className: "text-right",
|
|
165
165
|
children: "AUM"
|
|
166
166
|
})
|
|
167
|
-
] }) }), /* @__PURE__ */
|
|
168
|
-
/* @__PURE__ */
|
|
167
|
+
] }) }), /* @__PURE__ */ w(V, { children: Z.byAdvisor.map((e) => /* @__PURE__ */ T(G, { children: [
|
|
168
|
+
/* @__PURE__ */ w(H, {
|
|
169
169
|
className: "font-medium",
|
|
170
170
|
children: e.displayName ?? e.email ?? e.advisorId
|
|
171
171
|
}),
|
|
172
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ w(H, { children: /* @__PURE__ */ w(E, {
|
|
173
173
|
variant: e.role === "FIRM_ADMIN" ? "default" : "secondary",
|
|
174
174
|
children: e.role ?? "—"
|
|
175
175
|
}) }),
|
|
176
|
-
/* @__PURE__ */
|
|
177
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ w(H, { children: e.householdCount }),
|
|
177
|
+
/* @__PURE__ */ w(H, {
|
|
178
178
|
className: "text-right font-medium",
|
|
179
|
-
children:
|
|
179
|
+
children: h(e.aum, Z.currency)
|
|
180
180
|
})
|
|
181
181
|
] }, e.advisorId)) })] })]
|
|
182
182
|
}) })] }),
|
|
183
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ T(O, { children: [/* @__PURE__ */ w(j, {
|
|
184
184
|
className: "flex flex-row items-center justify-between gap-4",
|
|
185
|
-
children: /* @__PURE__ */
|
|
186
|
-
}), /* @__PURE__ */
|
|
185
|
+
children: /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w(M, { children: e("brainyrich.firm.roster.title", "Firm Roster") }), /* @__PURE__ */ w(A, { children: e("brainyrich.firm.roster.description", "Every advisor on the firm roster. Each active advisor uses one advisor seat on your plan; deactivate an advisor to free their seat.") })] })
|
|
186
|
+
}), /* @__PURE__ */ T(k, {
|
|
187
187
|
className: "space-y-4",
|
|
188
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ T("div", {
|
|
189
189
|
className: "flex flex-wrap items-end gap-3 rounded-lg border border-border-subtle bg-bg-sunken p-3",
|
|
190
190
|
children: [
|
|
191
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ T("div", {
|
|
192
192
|
className: "space-y-1.5",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
193
|
+
children: [/* @__PURE__ */ w(P, {
|
|
194
194
|
htmlFor: "invite-user-id",
|
|
195
195
|
children: e("brainyrich.firm.roster.inviteUserId", "User ID to invite")
|
|
196
|
-
}), /* @__PURE__ */
|
|
196
|
+
}), /* @__PURE__ */ w(N, {
|
|
197
197
|
id: "invite-user-id",
|
|
198
|
-
value:
|
|
199
|
-
onChange: (e) =>
|
|
198
|
+
value: x,
|
|
199
|
+
onChange: (e) => C(e.target.value),
|
|
200
200
|
className: "w-64",
|
|
201
201
|
placeholder: "actor id"
|
|
202
202
|
})]
|
|
203
203
|
}),
|
|
204
|
-
/* @__PURE__ */
|
|
204
|
+
/* @__PURE__ */ T("div", {
|
|
205
205
|
className: "space-y-1.5",
|
|
206
|
-
children: [/* @__PURE__ */
|
|
207
|
-
value:
|
|
208
|
-
onValueChange: (e) =>
|
|
209
|
-
children: [/* @__PURE__ */
|
|
206
|
+
children: [/* @__PURE__ */ w(P, { children: e("brainyrich.firm.roster.inviteRole", "Role") }), /* @__PURE__ */ T(F, {
|
|
207
|
+
value: J,
|
|
208
|
+
onValueChange: (e) => Y(e),
|
|
209
|
+
children: [/* @__PURE__ */ w(R, {
|
|
210
210
|
className: "w-40",
|
|
211
|
-
children: /* @__PURE__ */
|
|
212
|
-
}), /* @__PURE__ */
|
|
211
|
+
children: /* @__PURE__ */ w(z, {})
|
|
212
|
+
}), /* @__PURE__ */ T(I, { children: [/* @__PURE__ */ w(L, {
|
|
213
213
|
value: "ADVISOR",
|
|
214
214
|
children: "Advisor"
|
|
215
|
-
}), /* @__PURE__ */
|
|
215
|
+
}), /* @__PURE__ */ w(L, {
|
|
216
216
|
value: "FIRM_ADMIN",
|
|
217
217
|
children: "Firm Admin"
|
|
218
218
|
})] })]
|
|
219
219
|
})]
|
|
220
220
|
}),
|
|
221
|
-
/* @__PURE__ */
|
|
222
|
-
disabled: !
|
|
223
|
-
onClick: () =>
|
|
224
|
-
userId:
|
|
225
|
-
role:
|
|
226
|
-
}, { onSuccess: () =>
|
|
227
|
-
children: [/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ T(D, {
|
|
222
|
+
disabled: !x.trim() || X.isPending,
|
|
223
|
+
onClick: () => X.mutate({
|
|
224
|
+
userId: x.trim(),
|
|
225
|
+
role: J
|
|
226
|
+
}, { onSuccess: () => C("") }),
|
|
227
|
+
children: [/* @__PURE__ */ w(y, {
|
|
228
228
|
className: "size-4",
|
|
229
229
|
"aria-hidden": "true"
|
|
230
230
|
}), e("brainyrich.firm.roster.invite", "Invite")]
|
|
231
231
|
})
|
|
232
232
|
]
|
|
233
|
-
}),
|
|
234
|
-
icon: /* @__PURE__ */
|
|
233
|
+
}), p.isLoading ? /* @__PURE__ */ w(f, {}) : p.isError ? /* @__PURE__ */ w(d, { onRetry: () => p.refetch() }) : p.data.length === 0 ? /* @__PURE__ */ w(u, {
|
|
234
|
+
icon: /* @__PURE__ */ w(S, {
|
|
235
235
|
className: "size-8",
|
|
236
236
|
"aria-hidden": "true"
|
|
237
237
|
}),
|
|
238
238
|
title: e("brainyrich.firm.roster.empty", "No advisors yet")
|
|
239
|
-
}) : /* @__PURE__ */
|
|
240
|
-
/* @__PURE__ */
|
|
241
|
-
/* @__PURE__ */
|
|
242
|
-
/* @__PURE__ */
|
|
243
|
-
/* @__PURE__ */
|
|
244
|
-
/* @__PURE__ */
|
|
239
|
+
}) : /* @__PURE__ */ T(B, { children: [/* @__PURE__ */ w(W, { children: /* @__PURE__ */ T(G, { children: [
|
|
240
|
+
/* @__PURE__ */ w(U, { children: "Name" }),
|
|
241
|
+
/* @__PURE__ */ w(U, { children: "Email" }),
|
|
242
|
+
/* @__PURE__ */ w(U, { children: "Status" }),
|
|
243
|
+
/* @__PURE__ */ w(U, { children: "Role" }),
|
|
244
|
+
/* @__PURE__ */ w(U, {
|
|
245
245
|
className: "text-right",
|
|
246
246
|
children: "Actions"
|
|
247
247
|
})
|
|
248
|
-
] }) }), /* @__PURE__ */
|
|
249
|
-
/* @__PURE__ */
|
|
248
|
+
] }) }), /* @__PURE__ */ w(V, { children: p.data.map((t) => /* @__PURE__ */ T(G, { children: [
|
|
249
|
+
/* @__PURE__ */ w(H, {
|
|
250
250
|
className: "font-medium",
|
|
251
|
-
children:
|
|
251
|
+
children: t.displayName ?? t.userId
|
|
252
252
|
}),
|
|
253
|
-
/* @__PURE__ */
|
|
254
|
-
/* @__PURE__ */
|
|
255
|
-
variant:
|
|
256
|
-
children:
|
|
253
|
+
/* @__PURE__ */ w(H, { children: t.email ?? "—" }),
|
|
254
|
+
/* @__PURE__ */ w(H, { children: /* @__PURE__ */ w(E, {
|
|
255
|
+
variant: t.status === "ACTIVE" ? "default" : "secondary",
|
|
256
|
+
children: t.status
|
|
257
257
|
}) }),
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
-
value:
|
|
260
|
-
onValueChange: (
|
|
261
|
-
userId:
|
|
262
|
-
role:
|
|
258
|
+
/* @__PURE__ */ w(H, { children: /* @__PURE__ */ T(F, {
|
|
259
|
+
value: t.role,
|
|
260
|
+
onValueChange: (e) => g.mutate({
|
|
261
|
+
userId: t.userId,
|
|
262
|
+
role: e
|
|
263
263
|
}),
|
|
264
|
-
children: [/* @__PURE__ */
|
|
264
|
+
children: [/* @__PURE__ */ w(R, {
|
|
265
265
|
className: "w-40",
|
|
266
|
-
children: /* @__PURE__ */
|
|
267
|
-
}), /* @__PURE__ */
|
|
266
|
+
children: /* @__PURE__ */ w(z, {})
|
|
267
|
+
}), /* @__PURE__ */ T(I, { children: [/* @__PURE__ */ w(L, {
|
|
268
268
|
value: "ADVISOR",
|
|
269
269
|
children: "Advisor"
|
|
270
|
-
}), /* @__PURE__ */
|
|
270
|
+
}), /* @__PURE__ */ w(L, {
|
|
271
271
|
value: "FIRM_ADMIN",
|
|
272
272
|
children: "Firm Admin"
|
|
273
273
|
})] })]
|
|
274
274
|
}) }),
|
|
275
|
-
/* @__PURE__ */
|
|
276
|
-
className: "text-right
|
|
277
|
-
children:
|
|
275
|
+
/* @__PURE__ */ w(H, {
|
|
276
|
+
className: "text-right",
|
|
277
|
+
children: /* @__PURE__ */ T("div", {
|
|
278
|
+
className: "flex items-center justify-end gap-3",
|
|
279
|
+
children: [/* @__PURE__ */ w("span", {
|
|
280
|
+
className: "text-xs text-text-secondary",
|
|
281
|
+
children: t.invitedById ? `Invited by ${t.invitedById}` : "Self-registered"
|
|
282
|
+
}), /* @__PURE__ */ w(D, {
|
|
283
|
+
size: "sm",
|
|
284
|
+
variant: "outline",
|
|
285
|
+
disabled: b.isPending,
|
|
286
|
+
onClick: () => b.mutate({
|
|
287
|
+
userId: t.userId,
|
|
288
|
+
status: t.status === "ACTIVE" ? "INACTIVE" : "ACTIVE"
|
|
289
|
+
}),
|
|
290
|
+
children: t.status === "ACTIVE" ? e("brainyrich.firm.roster.deactivate", "Deactivate") : e("brainyrich.firm.roster.reactivate", "Reactivate")
|
|
291
|
+
})]
|
|
292
|
+
})
|
|
278
293
|
})
|
|
279
|
-
] },
|
|
294
|
+
] }, t.id)) })] })]
|
|
280
295
|
})] })
|
|
281
296
|
]
|
|
282
297
|
});
|
|
283
298
|
}
|
|
284
299
|
//#endregion
|
|
285
|
-
export {
|
|
300
|
+
export { J as FirmPage };
|
|
286
301
|
|
|
287
302
|
//# sourceMappingURL=FirmPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FirmPage.js","names":[],"sources":["../../../src/brainyrich/pages/FirmPage.tsx"],"sourcesContent":["/**\n * FirmPage — firm roster + firm-wide reporting (BOFF-4247).\n *\n * `braFirmReport` / `braListAdvisors` are FIRM_ADMIN-only server-side (see\n * firm.gql) — the domain data (BraAdvisor.role) is the only authorization\n * fact, there is no separate platform-RBAC role for it. This page therefore\n * always renders the calling actor's own roster status first, and only\n * attempts the firm-wide views when that status is FIRM_ADMIN (or when the\n * actor hasn't registered a roster row yet, in which case it offers\n * self-registration — the very first advisor in a workspace becomes\n * FIRM_ADMIN automatically).\n */\nimport { useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\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} from '@burdenoff/fe-libs/ui';\nimport { Building2, PlusCircle, ShieldCheck, UserPlus, Users } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n useMyAdvisorProfile,\n useEnsureAdvisorProfile,\n useAdvisors,\n useSetAdvisorRole,\n useFirmReport,\n} from '../hooks/useBrainyRichApi';\nimport type { BraAdvisor, BraAdvisorRole } from '../types/bra';\nimport {\n AllocationBars,\n SectionShell,\n EmptyState,\n LoadingState,\n ErrorState,\n formatMoney,\n formatDate,\n formatPct,\n type AllocationDatum,\n} from './_shared';\nimport { StatTile, FadeIn } from '../components/viz';\n\n/** Largest books charted as a share of firm AUM; the table below lists them all. */\nconst MAX_CHARTED_ADVISORS = 8;\n\ninterface AdvisorBookRow {\n advisorId: string;\n aum: number;\n displayName?: string | null;\n email?: string | null;\n}\n\nfunction advisorShareBars(\n byAdvisor: readonly AdvisorBookRow[],\n totalAum: number,\n currency: string\n): AllocationDatum[] {\n if (totalAum <= 0) return [];\n return byAdvisor.slice(0, MAX_CHARTED_ADVISORS).map((row) => {\n const share = (row.aum / totalAum) * 100;\n return {\n label: row.displayName ?? row.email ?? row.advisorId,\n value: share,\n tone: 'accent' as const,\n caption: `${formatMoney(row.aum, currency)} · ${formatPct(share)}`,\n };\n });\n}\n\ntype Translate = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string;\n\nexport function FirmPage() {\n const { t } = useI18n();\n const translate: Translate = (key, fallback, params) => tWithFallback(t, key, fallback, params);\n\n const myProfileQuery = useMyAdvisorProfile();\n const ensureProfile = useEnsureAdvisorProfile();\n const myProfile = myProfileQuery.data;\n const isFirmAdmin = myProfile?.role === 'FIRM_ADMIN';\n\n const [displayName, setDisplayName] = useState('');\n const [email, setEmail] = useState('');\n\n if (myProfileQuery.isLoading) {\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <LoadingState />\n </SectionShell>\n );\n }\n\n if (!myProfile) {\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <FadeIn>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Building2 className=\"size-4\" aria-hidden=\"true\" />\n {translate('brainyrich.firm.join.title', 'Join the firm roster')}\n </CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.join.description',\n \"You're not on this workspace's BrainyRich firm roster yet. The first advisor to register becomes the firm admin automatically; anyone after that is invited by an existing admin.\"\n )}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"firm-display-name\">\n {translate('brainyrich.firm.fields.displayName', 'Display name')}\n </Label>\n <Input\n id=\"firm-display-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"firm-email\">\n {translate('brainyrich.firm.fields.email', 'Email')}\n </Label>\n <Input\n id=\"firm-email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n </div>\n <Button\n disabled={ensureProfile.isPending}\n onClick={() =>\n ensureProfile.mutate({\n displayName: displayName || undefined,\n email: email || undefined,\n })\n }\n >\n <UserPlus className=\"size-4\" aria-hidden=\"true\" />\n {ensureProfile.isPending ? 'Registering…' : 'Register on firm roster'}\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n </SectionShell>\n );\n }\n\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <FadeIn>\n <Card>\n <CardContent className=\"flex flex-wrap items-center justify-between gap-3 py-4\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {translate('brainyrich.firm.you', 'You are on the firm roster as')}\n </span>\n <Badge variant={isFirmAdmin ? 'default' : 'secondary'}>{myProfile.role}</Badge>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {isFirmAdmin ? (\n <FirmAdminView translate={translate} />\n ) : (\n <FadeIn>\n <EmptyState\n icon={<Users className=\"size-8\" aria-hidden=\"true\" />}\n title={translate(\n 'brainyrich.firm.notAdmin',\n 'Firm-wide roster and AUM reporting are visible to firm admins only.'\n )}\n />\n </FadeIn>\n )}\n </SectionShell>\n );\n}\n\nfunction FirmAdminView({ translate }: { translate: Translate }) {\n const firmReportQuery = useFirmReport();\n const advisorsQuery = useAdvisors();\n const setRole = useSetAdvisorRole();\n const [inviteUserId, setInviteUserId] = useState('');\n const [inviteRole, setInviteRole] = useState<BraAdvisorRole>('ADVISOR');\n const ensureProfile = useEnsureAdvisorProfile();\n\n const report = firmReportQuery.data;\n\n return (\n <div className=\"space-y-6\">\n <FadeIn>\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n <StatTile\n label={translate('brainyrich.firm.kpis.totalAum', 'Firm AUM')}\n value={report ? formatMoney(report.totalAum, report.currency) : '—'}\n glow\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.advisors', 'Advisors')}\n value={report ? String(report.totalAdvisors) : '—'}\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.households', 'Households')}\n value={report ? String(report.totalHouseholds) : '—'}\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.asOf', 'As of')}\n value={report ? formatDate(report.asOf) : '—'}\n />\n </div>\n </FadeIn>\n\n <Card>\n <CardHeader>\n <CardTitle>{translate('brainyrich.firm.byAdvisor.title', 'AUM by advisor')}</CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.byAdvisor.description',\n 'Sorted by AUM, largest book first — a real rollup of BraHousehold.aum per advisor.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {firmReportQuery.isLoading ? (\n <LoadingState />\n ) : firmReportQuery.isError ? (\n <ErrorState onRetry={() => firmReportQuery.refetch()} />\n ) : !report || report.byAdvisor.length === 0 ? (\n <EmptyState\n icon={<Building2 className=\"size-8\" aria-hidden=\"true\" />}\n title={translate('brainyrich.firm.byAdvisor.empty', 'No advisor books yet')}\n />\n ) : (\n <div className=\"space-y-6\">\n {advisorShareBars(report.byAdvisor, report.totalAum, report.currency).length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {translate('brainyrich.firm.byAdvisor.shareLabel', 'Share of firm AUM')}\n </p>\n <AllocationBars\n data={advisorShareBars(report.byAdvisor, report.totalAum, report.currency)}\n />\n {report.byAdvisor.length > MAX_CHARTED_ADVISORS ? (\n <p className=\"text-xs text-text-secondary\">\n Showing the {MAX_CHARTED_ADVISORS} largest books — every advisor is listed\n below.\n </p>\n ) : null}\n </div>\n ) : null}\n\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Advisor</TableHead>\n <TableHead>Role</TableHead>\n <TableHead>Households</TableHead>\n <TableHead className=\"text-right\">AUM</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {report.byAdvisor.map((row) => (\n <TableRow key={row.advisorId}>\n <TableCell className=\"font-medium\">\n {row.displayName ?? row.email ?? row.advisorId}\n </TableCell>\n <TableCell>\n <Badge variant={row.role === 'FIRM_ADMIN' ? 'default' : 'secondary'}>\n {row.role ?? '—'}\n </Badge>\n </TableCell>\n <TableCell>{row.householdCount}</TableCell>\n <TableCell className=\"text-right font-medium\">\n {formatMoney(row.aum, report.currency)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-4\">\n <div>\n <CardTitle>{translate('brainyrich.firm.roster.title', 'Firm Roster')}</CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.roster.description',\n 'Every advisor on the firm roster, with the right to change roles.'\n )}\n </CardDescription>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3 rounded-lg border border-border-subtle bg-bg-sunken p-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"invite-user-id\">\n {translate('brainyrich.firm.roster.inviteUserId', 'User ID to invite')}\n </Label>\n <Input\n id=\"invite-user-id\"\n value={inviteUserId}\n onChange={(e) => setInviteUserId(e.target.value)}\n className=\"w-64\"\n placeholder=\"actor id\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label>{translate('brainyrich.firm.roster.inviteRole', 'Role')}</Label>\n <Select value={inviteRole} onValueChange={(v) => setInviteRole(v as BraAdvisorRole)}>\n <SelectTrigger className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ADVISOR\">Advisor</SelectItem>\n <SelectItem value=\"FIRM_ADMIN\">Firm Admin</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <Button\n disabled={!inviteUserId.trim() || ensureProfile.isPending}\n onClick={() =>\n ensureProfile.mutate(\n { userId: inviteUserId.trim(), role: inviteRole },\n { onSuccess: () => setInviteUserId('') }\n )\n }\n >\n <PlusCircle className=\"size-4\" aria-hidden=\"true\" />\n {translate('brainyrich.firm.roster.invite', 'Invite')}\n </Button>\n </div>\n\n {advisorsQuery.isLoading ? (\n <LoadingState />\n ) : advisorsQuery.isError ? (\n <ErrorState onRetry={() => advisorsQuery.refetch()} />\n ) : advisorsQuery.data.length === 0 ? (\n <EmptyState\n icon={<Users className=\"size-8\" aria-hidden=\"true\" />}\n title={translate('brainyrich.firm.roster.empty', 'No advisors yet')}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Email</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Role</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {advisorsQuery.data.map((advisor: BraAdvisor) => (\n <TableRow key={advisor.id}>\n <TableCell className=\"font-medium\">\n {advisor.displayName ?? advisor.userId}\n </TableCell>\n <TableCell>{advisor.email ?? '—'}</TableCell>\n <TableCell>\n <Badge variant={advisor.status === 'ACTIVE' ? 'default' : 'secondary'}>\n {advisor.status}\n </Badge>\n </TableCell>\n <TableCell>\n <Select\n value={advisor.role}\n onValueChange={(v) =>\n setRole.mutate({ userId: advisor.userId, role: v as BraAdvisorRole })\n }\n >\n <SelectTrigger className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ADVISOR\">Advisor</SelectItem>\n <SelectItem value=\"FIRM_ADMIN\">Firm Admin</SelectItem>\n </SelectContent>\n </Select>\n </TableCell>\n <TableCell className=\"text-right text-xs text-text-secondary\">\n {advisor.invitedById\n ? `Invited by ${advisor.invitedById}`\n : 'Self-registered'}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FirmPage.js","names":[],"sources":["../../../src/brainyrich/pages/FirmPage.tsx"],"sourcesContent":["/**\n * FirmPage — firm roster + firm-wide reporting (BOFF-4247).\n *\n * `braFirmReport` / `braListAdvisors` are FIRM_ADMIN-only server-side (see\n * firm.gql) — the domain data (BraAdvisor.role) is the only authorization\n * fact, there is no separate platform-RBAC role for it. This page therefore\n * always renders the calling actor's own roster status first, and only\n * attempts the firm-wide views when that status is FIRM_ADMIN (or when the\n * actor hasn't registered a roster row yet, in which case it offers\n * self-registration — the very first advisor in a workspace becomes\n * FIRM_ADMIN automatically).\n */\nimport { useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\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} from '@burdenoff/fe-libs/ui';\nimport { Building2, PlusCircle, ShieldCheck, UserPlus, Users } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n useMyAdvisorProfile,\n useEnsureAdvisorProfile,\n useAdvisors,\n useSetAdvisorRole,\n useSetAdvisorStatus,\n useFirmReport,\n} from '../hooks/useBrainyRichApi';\nimport type { BraAdvisor, BraAdvisorRole } from '../types/bra';\nimport {\n AllocationBars,\n SectionShell,\n EmptyState,\n LoadingState,\n ErrorState,\n formatMoney,\n formatDate,\n formatPct,\n type AllocationDatum,\n} from './_shared';\nimport { StatTile, FadeIn } from '../components/viz';\n\n/** Largest books charted as a share of firm AUM; the table below lists them all. */\nconst MAX_CHARTED_ADVISORS = 8;\n\ninterface AdvisorBookRow {\n advisorId: string;\n aum: number;\n displayName?: string | null;\n email?: string | null;\n}\n\nfunction advisorShareBars(\n byAdvisor: readonly AdvisorBookRow[],\n totalAum: number,\n currency: string\n): AllocationDatum[] {\n if (totalAum <= 0) return [];\n return byAdvisor.slice(0, MAX_CHARTED_ADVISORS).map((row) => {\n const share = (row.aum / totalAum) * 100;\n return {\n label: row.displayName ?? row.email ?? row.advisorId,\n value: share,\n tone: 'accent' as const,\n caption: `${formatMoney(row.aum, currency)} · ${formatPct(share)}`,\n };\n });\n}\n\ntype Translate = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string;\n\nexport function FirmPage() {\n const { t } = useI18n();\n const translate: Translate = (key, fallback, params) => tWithFallback(t, key, fallback, params);\n\n const myProfileQuery = useMyAdvisorProfile();\n const ensureProfile = useEnsureAdvisorProfile();\n const myProfile = myProfileQuery.data;\n const isFirmAdmin = myProfile?.role === 'FIRM_ADMIN';\n\n const [displayName, setDisplayName] = useState('');\n const [email, setEmail] = useState('');\n\n if (myProfileQuery.isLoading) {\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <LoadingState />\n </SectionShell>\n );\n }\n\n if (!myProfile) {\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <FadeIn>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Building2 className=\"size-4\" aria-hidden=\"true\" />\n {translate('brainyrich.firm.join.title', 'Join the firm roster')}\n </CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.join.description',\n \"You're not on this workspace's BrainyRich firm roster yet. The first advisor to register becomes the firm admin automatically; anyone after that is invited by an existing admin.\"\n )}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"firm-display-name\">\n {translate('brainyrich.firm.fields.displayName', 'Display name')}\n </Label>\n <Input\n id=\"firm-display-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"firm-email\">\n {translate('brainyrich.firm.fields.email', 'Email')}\n </Label>\n <Input\n id=\"firm-email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n </div>\n <Button\n disabled={ensureProfile.isPending}\n onClick={() =>\n ensureProfile.mutate({\n displayName: displayName || undefined,\n email: email || undefined,\n })\n }\n >\n <UserPlus className=\"size-4\" aria-hidden=\"true\" />\n {ensureProfile.isPending ? 'Registering…' : 'Register on firm roster'}\n </Button>\n </CardContent>\n </Card>\n </FadeIn>\n </SectionShell>\n );\n }\n\n return (\n <SectionShell\n title={translate('brainyrich.firm.title', 'Firm')}\n description={translate(\n 'brainyrich.firm.description',\n 'Firm roster and firm-wide AUM reporting.'\n )}\n >\n <FadeIn>\n <Card>\n <CardContent className=\"flex flex-wrap items-center justify-between gap-3 py-4\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {translate('brainyrich.firm.you', 'You are on the firm roster as')}\n </span>\n <Badge variant={isFirmAdmin ? 'default' : 'secondary'}>{myProfile.role}</Badge>\n </div>\n </CardContent>\n </Card>\n </FadeIn>\n\n {isFirmAdmin ? (\n <FirmAdminView translate={translate} />\n ) : (\n <FadeIn>\n <EmptyState\n icon={<Users className=\"size-8\" aria-hidden=\"true\" />}\n title={translate(\n 'brainyrich.firm.notAdmin',\n 'Firm-wide roster and AUM reporting are visible to firm admins only.'\n )}\n />\n </FadeIn>\n )}\n </SectionShell>\n );\n}\n\nfunction FirmAdminView({ translate }: { translate: Translate }) {\n const firmReportQuery = useFirmReport();\n const advisorsQuery = useAdvisors();\n const setRole = useSetAdvisorRole();\n const setStatus = useSetAdvisorStatus();\n const [inviteUserId, setInviteUserId] = useState('');\n const [inviteRole, setInviteRole] = useState<BraAdvisorRole>('ADVISOR');\n const ensureProfile = useEnsureAdvisorProfile();\n\n const report = firmReportQuery.data;\n\n return (\n <div className=\"space-y-6\">\n <FadeIn>\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n <StatTile\n label={translate('brainyrich.firm.kpis.totalAum', 'Firm AUM')}\n value={report ? formatMoney(report.totalAum, report.currency) : '—'}\n glow\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.advisors', 'Advisors')}\n value={report ? String(report.totalAdvisors) : '—'}\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.households', 'Households')}\n value={report ? String(report.totalHouseholds) : '—'}\n />\n <StatTile\n label={translate('brainyrich.firm.kpis.asOf', 'As of')}\n value={report ? formatDate(report.asOf) : '—'}\n />\n </div>\n </FadeIn>\n\n <Card>\n <CardHeader>\n <CardTitle>{translate('brainyrich.firm.byAdvisor.title', 'AUM by advisor')}</CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.byAdvisor.description',\n 'Sorted by AUM, largest book first — a real rollup of BraHousehold.aum per advisor.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {firmReportQuery.isLoading ? (\n <LoadingState />\n ) : firmReportQuery.isError ? (\n <ErrorState onRetry={() => firmReportQuery.refetch()} />\n ) : !report || report.byAdvisor.length === 0 ? (\n <EmptyState\n icon={<Building2 className=\"size-8\" aria-hidden=\"true\" />}\n title={translate('brainyrich.firm.byAdvisor.empty', 'No advisor books yet')}\n />\n ) : (\n <div className=\"space-y-6\">\n {advisorShareBars(report.byAdvisor, report.totalAum, report.currency).length > 0 ? (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {translate('brainyrich.firm.byAdvisor.shareLabel', 'Share of firm AUM')}\n </p>\n <AllocationBars\n data={advisorShareBars(report.byAdvisor, report.totalAum, report.currency)}\n />\n {report.byAdvisor.length > MAX_CHARTED_ADVISORS ? (\n <p className=\"text-xs text-text-secondary\">\n Showing the {MAX_CHARTED_ADVISORS} largest books — every advisor is listed\n below.\n </p>\n ) : null}\n </div>\n ) : null}\n\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Advisor</TableHead>\n <TableHead>Role</TableHead>\n <TableHead>Households</TableHead>\n <TableHead className=\"text-right\">AUM</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {report.byAdvisor.map((row) => (\n <TableRow key={row.advisorId}>\n <TableCell className=\"font-medium\">\n {row.displayName ?? row.email ?? row.advisorId}\n </TableCell>\n <TableCell>\n <Badge variant={row.role === 'FIRM_ADMIN' ? 'default' : 'secondary'}>\n {row.role ?? '—'}\n </Badge>\n </TableCell>\n <TableCell>{row.householdCount}</TableCell>\n <TableCell className=\"text-right font-medium\">\n {formatMoney(row.aum, report.currency)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-4\">\n <div>\n <CardTitle>{translate('brainyrich.firm.roster.title', 'Firm Roster')}</CardTitle>\n <CardDescription>\n {translate(\n 'brainyrich.firm.roster.description',\n 'Every advisor on the firm roster. Each active advisor uses one advisor seat on your plan; deactivate an advisor to free their seat.'\n )}\n </CardDescription>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-wrap items-end gap-3 rounded-lg border border-border-subtle bg-bg-sunken p-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"invite-user-id\">\n {translate('brainyrich.firm.roster.inviteUserId', 'User ID to invite')}\n </Label>\n <Input\n id=\"invite-user-id\"\n value={inviteUserId}\n onChange={(e) => setInviteUserId(e.target.value)}\n className=\"w-64\"\n placeholder=\"actor id\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label>{translate('brainyrich.firm.roster.inviteRole', 'Role')}</Label>\n <Select value={inviteRole} onValueChange={(v) => setInviteRole(v as BraAdvisorRole)}>\n <SelectTrigger className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ADVISOR\">Advisor</SelectItem>\n <SelectItem value=\"FIRM_ADMIN\">Firm Admin</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <Button\n disabled={!inviteUserId.trim() || ensureProfile.isPending}\n onClick={() =>\n ensureProfile.mutate(\n { userId: inviteUserId.trim(), role: inviteRole },\n { onSuccess: () => setInviteUserId('') }\n )\n }\n >\n <PlusCircle className=\"size-4\" aria-hidden=\"true\" />\n {translate('brainyrich.firm.roster.invite', 'Invite')}\n </Button>\n </div>\n\n {advisorsQuery.isLoading ? (\n <LoadingState />\n ) : advisorsQuery.isError ? (\n <ErrorState onRetry={() => advisorsQuery.refetch()} />\n ) : advisorsQuery.data.length === 0 ? (\n <EmptyState\n icon={<Users className=\"size-8\" aria-hidden=\"true\" />}\n title={translate('brainyrich.firm.roster.empty', 'No advisors yet')}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Email</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Role</TableHead>\n <TableHead className=\"text-right\">Actions</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {advisorsQuery.data.map((advisor: BraAdvisor) => (\n <TableRow key={advisor.id}>\n <TableCell className=\"font-medium\">\n {advisor.displayName ?? advisor.userId}\n </TableCell>\n <TableCell>{advisor.email ?? '—'}</TableCell>\n <TableCell>\n <Badge variant={advisor.status === 'ACTIVE' ? 'default' : 'secondary'}>\n {advisor.status}\n </Badge>\n </TableCell>\n <TableCell>\n <Select\n value={advisor.role}\n onValueChange={(v) =>\n setRole.mutate({ userId: advisor.userId, role: v as BraAdvisorRole })\n }\n >\n <SelectTrigger className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ADVISOR\">Advisor</SelectItem>\n <SelectItem value=\"FIRM_ADMIN\">Firm Admin</SelectItem>\n </SelectContent>\n </Select>\n </TableCell>\n <TableCell className=\"text-right\">\n <div className=\"flex items-center justify-end gap-3\">\n <span className=\"text-xs text-text-secondary\">\n {advisor.invitedById\n ? `Invited by ${advisor.invitedById}`\n : 'Self-registered'}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={setStatus.isPending}\n onClick={() =>\n setStatus.mutate({\n userId: advisor.userId,\n status: advisor.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE',\n })\n }\n >\n {advisor.status === 'ACTIVE'\n ? translate('brainyrich.firm.roster.deactivate', 'Deactivate')\n : translate('brainyrich.firm.roster.reactivate', 'Reactivate')}\n </Button>\n </div>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -639,6 +639,14 @@ export interface BraAggregationLink {
|
|
|
639
639
|
institutionName: string | null;
|
|
640
640
|
lastSyncedAt: string | null;
|
|
641
641
|
lastSyncError: string | null;
|
|
642
|
+
/** Plaid error code from the last failed sync or item webhook, e.g. `ITEM_LOGIN_REQUIRED`. */
|
|
643
|
+
errorCode: string | null;
|
|
644
|
+
/** When the institution's consent expires, if Plaid has warned that it will. */
|
|
645
|
+
consentExpiresAt: string | null;
|
|
646
|
+
/** True when only re-authenticating in Plaid Link update mode can repair the link. */
|
|
647
|
+
needsReauthentication: boolean;
|
|
648
|
+
/** When the scheduled daily re-sync next picks this link up. */
|
|
649
|
+
nextSyncAt: string | null;
|
|
642
650
|
createdAt: string;
|
|
643
651
|
updatedAt: string;
|
|
644
652
|
}
|
|
@@ -654,5 +662,7 @@ export interface BraAggregationSyncResult {
|
|
|
654
662
|
addedTransactions: number;
|
|
655
663
|
modifiedTransactions: number;
|
|
656
664
|
removedTransactions: number;
|
|
665
|
+
/** Investment positions written as holdings (0 when the institution shares none). */
|
|
666
|
+
holdingsSynced: number;
|
|
657
667
|
}
|
|
658
668
|
//# sourceMappingURL=bra.d.ts.map
|