@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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { useBrainyRich as e } from "../../providers/BrainyRichProvider.js";
|
|
2
|
+
import { wspaceFetch as t, wspaceMutate as n } from "./transport.js";
|
|
3
|
+
import { useState as r } from "react";
|
|
4
|
+
import { Plus as i, Tag as a, X as o } from "lucide-react";
|
|
5
|
+
import { useMutation as s, useQuery as c, useQueryClient as l } from "@tanstack/react-query";
|
|
6
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
import { Badge as f, Button as p, Input as m, Label as h, Skeleton as g } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
//#region src/brainyrich/components/reuse/TagField.tsx
|
|
9
|
+
var _ = "id label color key", v = "brainyrich", y = (e, t) => [
|
|
10
|
+
"brainyrich",
|
|
11
|
+
"entity-tags",
|
|
12
|
+
e,
|
|
13
|
+
t
|
|
14
|
+
], b = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "tag", x = (n, r) => {
|
|
15
|
+
let { apiGatewayUrl: i } = e();
|
|
16
|
+
return c({
|
|
17
|
+
queryKey: y(n, r ?? ""),
|
|
18
|
+
enabled: !!r,
|
|
19
|
+
queryFn: async () => (await t(`query BraGetEntityTags($entityType: String!, $entityId: ID!) {
|
|
20
|
+
getEntityTags(entityType: $entityType, entityId: $entityId) {
|
|
21
|
+
id
|
|
22
|
+
tag { ${_} }
|
|
23
|
+
}
|
|
24
|
+
}`, {
|
|
25
|
+
entityType: n,
|
|
26
|
+
entityId: r
|
|
27
|
+
}, i)).getEntityTags
|
|
28
|
+
});
|
|
29
|
+
}, S = (n) => {
|
|
30
|
+
let { apiGatewayUrl: r } = e(), i = n.trim();
|
|
31
|
+
return c({
|
|
32
|
+
queryKey: [
|
|
33
|
+
"brainyrich",
|
|
34
|
+
"tag-search",
|
|
35
|
+
i
|
|
36
|
+
],
|
|
37
|
+
enabled: i.length > 0,
|
|
38
|
+
queryFn: async () => (await t(`query BraSearchTags($query: String!, $limit: Int) {
|
|
39
|
+
searchTags(query: $query, limit: $limit) { ${_} }
|
|
40
|
+
}`, {
|
|
41
|
+
query: i,
|
|
42
|
+
limit: 8
|
|
43
|
+
}, r)).searchTags
|
|
44
|
+
});
|
|
45
|
+
}, C = async (e) => {
|
|
46
|
+
let r = await t("query BraGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }", { key: v }, e);
|
|
47
|
+
return r.getVocabularyByKey?.id ? r.getVocabularyByKey.id : (await n("mutation BraCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }", { input: {
|
|
48
|
+
key: v,
|
|
49
|
+
name: "BrainyRich",
|
|
50
|
+
description: "Tags for BrainyRich households, portfolios and goals.",
|
|
51
|
+
allowCustomTags: !0
|
|
52
|
+
} }, e)).createVocabulary.id;
|
|
53
|
+
}, w = (t, r) => {
|
|
54
|
+
let { apiGatewayUrl: i } = e(), a = l();
|
|
55
|
+
return s({
|
|
56
|
+
mutationFn: async (e) => (await n("mutation BraAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
|
|
57
|
+
entityType: t,
|
|
58
|
+
entityId: r,
|
|
59
|
+
tagId: e
|
|
60
|
+
} }, i)).assignTag,
|
|
61
|
+
onSuccess: () => a.invalidateQueries({ queryKey: y(t, r) })
|
|
62
|
+
});
|
|
63
|
+
}, T = (t, r) => {
|
|
64
|
+
let { apiGatewayUrl: i } = e(), a = l();
|
|
65
|
+
return s({
|
|
66
|
+
mutationFn: async (e) => {
|
|
67
|
+
let a = e.trim();
|
|
68
|
+
if (!a) throw Error("A tag label is required.");
|
|
69
|
+
let o = await n("mutation BraCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }", { input: {
|
|
70
|
+
vocabularyId: await C(i),
|
|
71
|
+
key: `${b(a)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
72
|
+
label: a
|
|
73
|
+
} }, i);
|
|
74
|
+
return await n("mutation BraAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
|
|
75
|
+
entityType: t,
|
|
76
|
+
entityId: r,
|
|
77
|
+
tagId: o.createTag.id
|
|
78
|
+
} }, i), o.createTag;
|
|
79
|
+
},
|
|
80
|
+
onSuccess: () => a.invalidateQueries({ queryKey: y(t, r) })
|
|
81
|
+
});
|
|
82
|
+
}, E = (t, r) => {
|
|
83
|
+
let { apiGatewayUrl: i } = e(), a = l();
|
|
84
|
+
return s({
|
|
85
|
+
mutationFn: async (e) => (await n("mutation BraRemoveTagAssignment($id: ID!) { removeTagAssignment(id: $id) }", { id: e }, i)).removeTagAssignment,
|
|
86
|
+
onSuccess: () => a.invalidateQueries({ queryKey: y(t, r) })
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
function D({ entityType: e, entityId: t, label: n = "Tags", hideLabel: s = !1 }) {
|
|
90
|
+
let [c, l] = r(""), _ = x(e, t), v = S(c), y = w(e, t), b = T(e, t), C = E(e, t), D = y.isPending || b.isPending, O = _.data ?? [], k = new Set(O.map((e) => e.tag.id)), A = (v.data ?? []).filter((e) => !k.has(e.id)), j = c.trim(), M = A.some((e) => e.label.toLowerCase() === j.toLowerCase()), N = `tagfield-${e}-${t}`, P = async () => {
|
|
91
|
+
!j || D || (await b.mutateAsync(j), l(""));
|
|
92
|
+
}, F = async (e) => {
|
|
93
|
+
D || (await y.mutateAsync(e), l(""));
|
|
94
|
+
};
|
|
95
|
+
return /* @__PURE__ */ d("div", {
|
|
96
|
+
className: "space-y-2",
|
|
97
|
+
children: [
|
|
98
|
+
!s && /* @__PURE__ */ u(h, {
|
|
99
|
+
htmlFor: N,
|
|
100
|
+
className: "text-text-primary",
|
|
101
|
+
children: n
|
|
102
|
+
}),
|
|
103
|
+
_.isLoading ? /* @__PURE__ */ d("div", {
|
|
104
|
+
className: "flex flex-wrap gap-2",
|
|
105
|
+
"aria-busy": "true",
|
|
106
|
+
children: [
|
|
107
|
+
/* @__PURE__ */ u(g, { className: "h-6 w-16 rounded-full" }),
|
|
108
|
+
/* @__PURE__ */ u(g, { className: "h-6 w-20 rounded-full" }),
|
|
109
|
+
/* @__PURE__ */ u(g, { className: "h-6 w-12 rounded-full" })
|
|
110
|
+
]
|
|
111
|
+
}) : _.isError ? /* @__PURE__ */ d("p", {
|
|
112
|
+
className: "text-sm text-status-error-text",
|
|
113
|
+
children: [
|
|
114
|
+
"Could not load tags.",
|
|
115
|
+
" ",
|
|
116
|
+
/* @__PURE__ */ u("button", {
|
|
117
|
+
type: "button",
|
|
118
|
+
className: "underline underline-offset-2",
|
|
119
|
+
onClick: () => _.refetch(),
|
|
120
|
+
children: "Retry"
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
}) : O.length === 0 ? /* @__PURE__ */ d("p", {
|
|
124
|
+
className: "flex items-center gap-1.5 text-sm text-text-secondary",
|
|
125
|
+
children: [/* @__PURE__ */ u(a, {
|
|
126
|
+
className: "size-3.5",
|
|
127
|
+
"aria-hidden": "true"
|
|
128
|
+
}), "No tags yet — add one below."]
|
|
129
|
+
}) : /* @__PURE__ */ u("div", {
|
|
130
|
+
className: "flex flex-wrap gap-2",
|
|
131
|
+
children: O.map((e) => /* @__PURE__ */ d(f, {
|
|
132
|
+
variant: "secondary",
|
|
133
|
+
className: "gap-1 pr-1",
|
|
134
|
+
children: [/* @__PURE__ */ u("span", { children: e.tag.label }), /* @__PURE__ */ u("button", {
|
|
135
|
+
type: "button",
|
|
136
|
+
"aria-label": `Remove tag ${e.tag.label}`,
|
|
137
|
+
className: "rounded-full p-0.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-border-focus disabled:opacity-50",
|
|
138
|
+
disabled: C.isPending,
|
|
139
|
+
onClick: () => C.mutate(e.id),
|
|
140
|
+
children: /* @__PURE__ */ u(o, {
|
|
141
|
+
className: "size-3",
|
|
142
|
+
"aria-hidden": "true"
|
|
143
|
+
})
|
|
144
|
+
})]
|
|
145
|
+
}, e.id))
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ d("div", {
|
|
148
|
+
className: "relative",
|
|
149
|
+
children: [/* @__PURE__ */ d("div", {
|
|
150
|
+
className: "flex gap-2",
|
|
151
|
+
children: [/* @__PURE__ */ u(m, {
|
|
152
|
+
id: N,
|
|
153
|
+
value: c,
|
|
154
|
+
onChange: (e) => l(e.target.value),
|
|
155
|
+
onKeyDown: (e) => {
|
|
156
|
+
e.key === "Enter" && (e.preventDefault(), P());
|
|
157
|
+
},
|
|
158
|
+
placeholder: "Search or create a tag…",
|
|
159
|
+
"aria-label": "Search or create a tag",
|
|
160
|
+
autoComplete: "off",
|
|
161
|
+
disabled: D
|
|
162
|
+
}), /* @__PURE__ */ d(p, {
|
|
163
|
+
type: "button",
|
|
164
|
+
variant: "outline",
|
|
165
|
+
size: "sm",
|
|
166
|
+
onClick: P,
|
|
167
|
+
disabled: !j || D || M,
|
|
168
|
+
children: [/* @__PURE__ */ u(i, {
|
|
169
|
+
className: "size-4",
|
|
170
|
+
"aria-hidden": "true"
|
|
171
|
+
}), "Create"]
|
|
172
|
+
})]
|
|
173
|
+
}), j.length > 0 && (A.length > 0 || v.isFetching) && /* @__PURE__ */ u("ul", {
|
|
174
|
+
className: "absolute z-10 mt-1 w-full overflow-hidden rounded-md border border-border-subtle bg-bg-surface shadow-md",
|
|
175
|
+
role: "listbox",
|
|
176
|
+
"aria-label": "Tag suggestions",
|
|
177
|
+
children: v.isFetching && A.length === 0 ? /* @__PURE__ */ u("li", {
|
|
178
|
+
className: "px-3 py-2 text-sm text-text-secondary",
|
|
179
|
+
children: "Searching…"
|
|
180
|
+
}) : A.map((e) => /* @__PURE__ */ u("li", {
|
|
181
|
+
role: "option",
|
|
182
|
+
"aria-selected": "false",
|
|
183
|
+
children: /* @__PURE__ */ d("button", {
|
|
184
|
+
type: "button",
|
|
185
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-text-primary hover:bg-bg-sunken focus-visible:bg-bg-sunken focus-visible:outline-none disabled:opacity-50",
|
|
186
|
+
disabled: D,
|
|
187
|
+
onClick: () => F(e.id),
|
|
188
|
+
children: [/* @__PURE__ */ u(a, {
|
|
189
|
+
className: "size-3.5 text-text-secondary",
|
|
190
|
+
"aria-hidden": "true"
|
|
191
|
+
}), e.label]
|
|
192
|
+
})
|
|
193
|
+
}, e.id))
|
|
194
|
+
})]
|
|
195
|
+
}),
|
|
196
|
+
(y.isError || b.isError || C.isError) && /* @__PURE__ */ u("p", {
|
|
197
|
+
className: "text-sm text-status-error-text",
|
|
198
|
+
children: "Could not update tags. Please try again."
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { D as TagField };
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=TagField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagField.js","names":[],"sources":["../../../../src/brainyrich/components/reuse/TagField.tsx"],"sourcesContent":["/**\n * TagField — REUSE of `wspace-tags-svc` UniversalTag (polymorphic-by-entity).\n * @module brainyrich/components/reuse/TagField\n *\n * Drop-in replacement for the free-text tag string inputs on Households /\n * Portfolios / Goals. Instead of writing a `tags` scalar on the bra entity,\n * this manages REAL `UniversalTag` assignments keyed by `(entityType, entityId)`\n * against the platform tag store — no bra-svc change required. The bra `tags`\n * scalar stays as legacy; this component is the canonical tag surface.\n *\n * Ops (all on the workspace supergraph):\n * - `getEntityTags(entityType, entityId)` → current assignments (chips)\n * - `searchTags(query, limit)` → typeahead over existing tags\n * - `assignTag(input)` → link an existing tag\n * - `createTag(input)` + `assignTag(...)` → create-from-label then link\n * - `removeTagAssignment(id)` → unlink a chip\n *\n * `createTag` needs a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"brainyrich\" vocabulary via `getVocabularyByKey` (creating it on first use).\n *\n * Presentational + self-contained: owns its react-query hooks, reads the gateway\n * base URL from `useBrainyRich()`, uses fe-libs/ui primitives + semantic tokens.\n */\n\nimport { useState, type KeyboardEvent } from 'react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { Plus, Tag as TagIcon, X } from 'lucide-react';\nimport { Badge, Button, Input, Label, Skeleton } from '@burdenoff/fe-libs/ui';\n\nimport { useBrainyRich } from '../../providers/BrainyRichProvider';\nimport { wspaceFetch, wspaceMutate } from './transport';\n\n// ============================================================================\n// Types (mirror the UniversalTag SDL fields we select)\n// ============================================================================\n\ninterface UniversalTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\ninterface UniversalTagAssignment {\n id: string;\n tag: UniversalTag;\n}\n\nconst TAG_FIELDS = 'id label color key';\nconst BRAINYRICH_VOCABULARY_KEY = 'brainyrich';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['brainyrich', 'entity-tags', entityType, entityId] as const;\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n// ============================================================================\n// Hooks (co-located; reuse the shared workspace transport)\n// ============================================================================\n\nconst useEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBrainyRich();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: UniversalTagAssignment[] }>(\n `query BraGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\nconst useTagSearch = (query: string) => {\n const { apiGatewayUrl } = useBrainyRich();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['brainyrich', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: UniversalTag[] }>(\n `query BraSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Resolve (creating on first use) the workspace-scoped \"brainyrich\" vocabulary. */\nconst resolveVocabularyId = async (baseUrl?: string): Promise<string> => {\n const existing = await wspaceFetch<{ getVocabularyByKey: { id: string } | null }>(\n `query BraGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }`,\n { key: BRAINYRICH_VOCABULARY_KEY },\n baseUrl\n );\n if (existing.getVocabularyByKey?.id) return existing.getVocabularyByKey.id;\n\n const created = await wspaceMutate<{ createVocabulary: { id: string } }>(\n `mutation BraCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }`,\n {\n input: {\n key: BRAINYRICH_VOCABULARY_KEY,\n name: 'BrainyRich',\n description: 'Tags for BrainyRich households, portfolios and goals.',\n allowCustomTags: true,\n },\n },\n baseUrl\n );\n return created.createVocabulary.id;\n};\n\nconst useAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBrainyRich();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceMutate<{ assignTag: { id: string } }>(\n `mutation BraAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\nconst useCreateAndAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBrainyRich();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (label: string) => {\n const trimmed = label.trim();\n if (!trimmed) throw new Error('A tag label is required.');\n const vocabularyId = await resolveVocabularyId(apiGatewayUrl);\n // Unique key avoids collisions inside the vocabulary; the label is the display text.\n const key = `${slugify(trimmed)}-${Math.random().toString(36).slice(2, 6)}`;\n const created = await wspaceMutate<{ createTag: { id: string } }>(\n `mutation BraCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }`,\n { input: { vocabularyId, key, label: trimmed } },\n apiGatewayUrl\n );\n await wspaceMutate<{ assignTag: { id: string } }>(\n `mutation BraAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId: created.createTag.id } },\n apiGatewayUrl\n );\n return created.createTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\nconst useRemoveTagAssignment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBrainyRich();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assignmentId: string) => {\n const data = await wspaceMutate<{ removeTagAssignment: boolean }>(\n `mutation BraRemoveTagAssignment($id: ID!) { removeTagAssignment(id: $id) }`,\n { id: assignmentId },\n apiGatewayUrl\n );\n return data.removeTagAssignment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport interface TagFieldProps {\n /** Polymorphic entity type, e.g. \"brainyrich-household\". */\n entityType: string;\n /** The bra entity's id (an ID scalar). */\n entityId: string;\n /** Optional field label (hidden visually when `hideLabel`). */\n label?: string;\n hideLabel?: boolean;\n}\n\nexport function TagField({\n entityType,\n entityId,\n label = 'Tags',\n hideLabel = false,\n}: TagFieldProps) {\n const [draft, setDraft] = useState('');\n const assignments = useEntityTags(entityType, entityId);\n const search = useTagSearch(draft);\n const assign = useAssignTag(entityType, entityId);\n const createAndAssign = useCreateAndAssignTag(entityType, entityId);\n const remove = useRemoveTagAssignment(entityType, entityId);\n\n const busy = assign.isPending || createAndAssign.isPending;\n const assigned = assignments.data ?? [];\n const assignedTagIds = new Set(assigned.map((a) => a.tag.id));\n const suggestions = (search.data ?? []).filter((t) => !assignedTagIds.has(t.id));\n const trimmed = draft.trim();\n const exactExists = suggestions.some((t) => t.label.toLowerCase() === trimmed.toLowerCase());\n\n const inputId = `tagfield-${entityType}-${entityId}`;\n\n const commitCreate = async () => {\n if (!trimmed || busy) return;\n await createAndAssign.mutateAsync(trimmed);\n setDraft('');\n };\n\n const commitAssign = async (tagId: string) => {\n if (busy) return;\n await assign.mutateAsync(tagId);\n setDraft('');\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n void commitCreate();\n }\n };\n\n return (\n <div className=\"space-y-2\">\n {!hideLabel && (\n <Label htmlFor={inputId} className=\"text-text-primary\">\n {label}\n </Label>\n )}\n\n {/* Current assignments */}\n {assignments.isLoading ? (\n <div className=\"flex flex-wrap gap-2\" aria-busy=\"true\">\n <Skeleton className=\"h-6 w-16 rounded-full\" />\n <Skeleton className=\"h-6 w-20 rounded-full\" />\n <Skeleton className=\"h-6 w-12 rounded-full\" />\n </div>\n ) : assignments.isError ? (\n <p className=\"text-sm text-status-error-text\">\n Could not load tags.{' '}\n <button\n type=\"button\"\n className=\"underline underline-offset-2\"\n onClick={() => assignments.refetch()}\n >\n Retry\n </button>\n </p>\n ) : assigned.length === 0 ? (\n <p className=\"flex items-center gap-1.5 text-sm text-text-secondary\">\n <TagIcon className=\"size-3.5\" aria-hidden=\"true\" />\n No tags yet — add one below.\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {assigned.map((a) => (\n <Badge key={a.id} variant=\"secondary\" className=\"gap-1 pr-1\">\n <span>{a.tag.label}</span>\n <button\n type=\"button\"\n aria-label={`Remove tag ${a.tag.label}`}\n className=\"rounded-full p-0.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-border-focus disabled:opacity-50\"\n disabled={remove.isPending}\n onClick={() => remove.mutate(a.id)}\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n\n {/* Add control */}\n <div className=\"relative\">\n <div className=\"flex gap-2\">\n <Input\n id={inputId}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder=\"Search or create a tag…\"\n aria-label=\"Search or create a tag\"\n autoComplete=\"off\"\n disabled={busy}\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={commitCreate}\n disabled={!trimmed || busy || exactExists}\n >\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n Create\n </Button>\n </div>\n\n {/* Typeahead suggestions */}\n {trimmed.length > 0 && (suggestions.length > 0 || search.isFetching) && (\n <ul\n className=\"absolute z-10 mt-1 w-full overflow-hidden rounded-md border border-border-subtle bg-bg-surface shadow-md\"\n role=\"listbox\"\n aria-label=\"Tag suggestions\"\n >\n {search.isFetching && suggestions.length === 0 ? (\n <li className=\"px-3 py-2 text-sm text-text-secondary\">Searching…</li>\n ) : (\n suggestions.map((t) => (\n <li key={t.id} role=\"option\" aria-selected=\"false\">\n <button\n type=\"button\"\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-text-primary hover:bg-bg-sunken focus-visible:bg-bg-sunken focus-visible:outline-none disabled:opacity-50\"\n disabled={busy}\n onClick={() => commitAssign(t.id)}\n >\n <TagIcon className=\"size-3.5 text-text-secondary\" aria-hidden=\"true\" />\n {t.label}\n </button>\n </li>\n ))\n )}\n </ul>\n )}\n </div>\n\n {(assign.isError || createAndAssign.isError || remove.isError) && (\n <p className=\"text-sm text-status-error-text\">\n Could not update tags. Please try again.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { graphqlFetch as e } from "@burdenoff/fe-libs/shared/graphql";
|
|
2
|
+
//#region src/brainyrich/components/reuse/transport.ts
|
|
3
|
+
var t = () => {
|
|
4
|
+
let e = globalThis;
|
|
5
|
+
e.__graphqlFetchResponseCache?.clear(), e.__graphqlFetchPendingResponses?.clear(), e.__boffGraphQLResponseCache?.clear();
|
|
6
|
+
}, n = async (t, n, r) => {
|
|
7
|
+
let i = await e({
|
|
8
|
+
gateway: "workspace",
|
|
9
|
+
baseUrl: r,
|
|
10
|
+
query: t,
|
|
11
|
+
variables: n,
|
|
12
|
+
workspaceToken: !0
|
|
13
|
+
});
|
|
14
|
+
if (i.errors?.length) throw Error(i.errors[0]?.message ?? "GraphQL request failed");
|
|
15
|
+
if (!i.data) throw Error("GraphQL response did not include data");
|
|
16
|
+
return i.data;
|
|
17
|
+
}, r = async (e, r, i) => {
|
|
18
|
+
let a = await n(e, r, i);
|
|
19
|
+
return t(), a;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { n as wspaceFetch, r as wspaceMutate };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","names":[],"sources":["../../../../src/brainyrich/components/reuse/transport.ts"],"sourcesContent":["/**\n * Shared workspace-gateway transport for the BrainyRich REUSE components.\n * @module brainyrich/components/reuse/transport\n *\n * The four reuse components (TagField, DocumentsPanel, CommentThread,\n * ReviewReminders) consume PLATFORM subgraphs — `wspace-tags-svc`,\n * `wspace-files-svc`, `wspace-conversations-svc`, `wspace-calendar-svc` —\n * that are composed into the SAME workspace supergraph the BrainyRich MFE\n * already talks to. So every call routes through the identical transport the\n * `bra*` hooks use: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })`.\n *\n * This file only contains the transport primitive; each component owns its own\n * react-query hooks (kept co-located so a page can drop one component into a\n * Sheet/tab without pulling in the others).\n */\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache keyed by operationName+variables (~15s); mutations are never cached. A\n * read issued right after a write (the react-query refetch our `invalidateQueries`\n * triggers) would otherwise return the PRE-mutation response until the TTL lapses,\n * hiding just-created rows. fe-libs' documented remedy is that write-performing\n * callers clear those caches — we do exactly that after every reuse mutation.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n return response.data;\n};\n\n/** Mutation transport — identical to `wspaceFetch` but clears read caches after. */\nexport const wspaceMutate = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const data = await wspaceFetch<TData>(query, variables, baseUrl);\n clearGraphqlReadCaches();\n return data;\n};\n"],"mappings":";;AAgCA,IAAM,UAAqC;CACzC,IAAM,IAAI;AAGV,CAFA,EAAE,6BAA6B,OAAO,EACtC,EAAE,gCAAgC,OAAO,EACzC,EAAE,4BAA4B,OAAO;GAI1B,IAAc,OACzB,GACA,GACA,MACmB;CACnB,IAAM,IAAW,MAAM,EAAoB;EACzC,SAAS;EACT;EACA;EACA;EACA,gBAAgB;EACjB,CAAC;AAEF,KAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,yBAAyB;AAE1E,KAAI,CAAC,EAAS,KACZ,OAAU,MAAM,wCAAwC;AAE1D,QAAO,EAAS;GAIL,IAAe,OAC1B,GACA,GACA,MACmB;CACnB,IAAM,IAAO,MAAM,EAAmB,GAAO,GAAW,EAAQ;AAEhE,QADA,GAAwB,EACjB"}
|