@burdenoff/microfe-planmagnet 2026.1008.3 → 2026.1008.4

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.
@@ -2,10 +2,10 @@ import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
2
2
  import { useTr as t } from "../../shared/hooks/useTr.js";
3
3
  import { isForbidden as n } from "../utils/graphqlErrors.js";
4
4
  import { ErrorLine as r } from "../controls/ConfirmButton.js";
5
- import { METRIC_TYPES as i, pageStateFromSearch as a, windowInput as o } from "../satisfaction/satisfaction.js";
6
- import { SatisfactionFigureCards as s } from "../satisfaction/SatisfactionFigures.js";
7
- import { CUSTOMER_SATISFACTION_SUMMARY as c, SATISFACTION_QUERIES as l } from "../satisfaction/satisfactionOperations.js";
8
- import { ProductSelect as ee, ProjectSelect as te } from "../controls/Pickers.js";
5
+ import { METRIC_TYPES as ee, pageStateFromSearch as i, windowInput as a } from "../satisfaction/satisfaction.js";
6
+ import { SatisfactionFigureCards as o } from "../satisfaction/SatisfactionFigures.js";
7
+ import { CUSTOMER_SATISFACTION_SUMMARY as s, SATISFACTION_QUERIES as c } from "../satisfaction/satisfactionOperations.js";
8
+ import { ProductSelect as l, ProjectSelect as te } from "../controls/Pickers.js";
9
9
  import { writeUrlParam as u } from "../utils/urlParam.js";
10
10
  import { SatisfactionReleaseSelect as d, useSatisfactionReleaseOptions as f } from "../satisfaction/SatisfactionReleaseSelect.js";
11
11
  import { SatisfactionImportDialog as p } from "../satisfaction/SatisfactionImportDialog.js";
@@ -13,27 +13,40 @@ import { SatisfactionResponseDialog as m } from "../satisfaction/SatisfactionRes
13
13
  import { SatisfactionResponsesTable as h } from "../satisfaction/SatisfactionResponsesTable.js";
14
14
  import { SatisfactionWindowPicker as g } from "../satisfaction/SatisfactionWindowPicker.js";
15
15
  import { useSatisfactionAccess as _ } from "../satisfaction/useSatisfactionAccess.js";
16
- import { useEffect as v, useMemo as y, useState as b } from "react";
17
- import { Info as x, Loader2 as S, MessageSquareHeart as C, X as ne } from "lucide-react";
18
- import { useLocation as re } from "react-router";
19
- import { useApolloClient as ie, useQuery as w } from "@apollo/client/react";
20
- import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
21
- import { Button as O, Card as k, CardContent as A, CardHeader as j, CardTitle as M } from "@burdenoff/fe-libs/ui";
22
- import { clearGraphqlFetchResponseCache as N } from "@burdenoff/fe-libs/shared/graphql";
16
+ import { useEffect as v, useMemo as y, useRef as b, useState as x } from "react";
17
+ import { Info as S, Loader2 as C, MessageSquareHeart as ne, X as re } from "lucide-react";
18
+ import { useLocation as ie } from "react-router";
19
+ import { useApolloClient as ae, useQuery as oe } from "@apollo/client/react";
20
+ import { Fragment as se, jsx as w, jsxs as T } from "react/jsx-runtime";
21
+ import { Button as ce, Card as E, CardContent as D, CardHeader as O, CardTitle as k } from "@burdenoff/fe-libs/ui";
22
+ import { clearGraphqlFetchResponseCache as A } from "@burdenoff/fe-libs/shared/graphql";
23
23
  //#region src/planmagnet/pages/ProductsCustomerSatisfactionPagePage.tsx
24
- function P() {
25
- let P = t(), { workspaceId: F } = e(), I = _(), L = ie(), { releases: R } = f(), { search: ae } = re(), [z, B] = b(() => a(ae, /* @__PURE__ */ new Date())), [V, H] = b(() => o(z.window) ?? { preset: "LAST_90_DAYS" }), [U, W] = b(null), G = (e) => {
24
+ function j() {
25
+ let j = t(), { workspaceId: M } = e(), N = _(), P = ae(), { releases: F } = f(), I = ie(), { search: L } = I, R = b({
26
+ key: I.key,
27
+ search: L
28
+ }), [z, B] = x(() => i(L, /* @__PURE__ */ new Date())), [V, H] = x(() => a(z.window) ?? { preset: "LAST_90_DAYS" });
29
+ v(() => {
30
+ if (R.current.key === I.key && R.current.search === L) return;
31
+ R.current = {
32
+ key: I.key,
33
+ search: L
34
+ };
35
+ let e = i(L, /* @__PURE__ */ new Date());
36
+ B(e), H(a(e.window) ?? { preset: "LAST_90_DAYS" });
37
+ }, [I.key, L]);
38
+ let [U, W] = x(null), G = (e) => {
26
39
  let t = {
27
40
  ...z,
28
41
  ...e
29
42
  };
30
43
  B(t);
31
- let n = o(t.window);
44
+ let n = a(t.window);
32
45
  n && H(n);
33
46
  };
34
47
  v(() => {
35
48
  let e = V.preset;
36
- u("window", e === "LAST_90_DAYS" ? null : e), u("from", e === "CUSTOM" ? V.from ?? null : null), u("to", e === "CUSTOM" ? V.to ?? null : null), u("product", z.productId || null), u("release", z.releaseId || null), u("project", z.projectId || null), u("metric", z.metricType || null);
49
+ u("window", e === "LAST_90_DAYS" ? null : e), u("from", e === "CUSTOM" ? V.from ?? null : null), u("to", e === "CUSTOM" ? V.to ?? null : null), u("product", z.productId || null), u("release", z.releaseId || null), u("satisfactionProject", z.projectId || null), u("metric", z.metricType || null);
37
50
  }, [
38
51
  V,
39
52
  z.productId,
@@ -61,115 +74,115 @@ function P() {
61
74
  productId: z.productId,
62
75
  releaseId: z.releaseId,
63
76
  projectId: z.projectId
64
- }, Y = !!(z.productId || z.releaseId || z.projectId), { data: X, previousData: oe, loading: Z, error: Q } = w(c, {
77
+ }, Y = !!(z.productId || z.releaseId || z.projectId), { data: X, previousData: le, loading: Z, error: Q } = oe(s, {
65
78
  variables: {
66
- workspaceId: F ?? "",
79
+ workspaceId: M ?? "",
67
80
  filter: {
68
81
  ...K,
69
82
  window: V
70
83
  }
71
84
  },
72
- skip: !F
73
- }), $ = (X ?? oe)?.planmagnetCustomerSatisfaction ?? null, se = Z && !X && !!$, ce = () => {
74
- N(), L.refetchQueries({ include: l });
85
+ skip: !M
86
+ }), $ = (X ?? le)?.planmagnetCustomerSatisfaction ?? null, ue = Z && !X && !!$, de = () => {
87
+ A(), P.refetchQueries({ include: c });
75
88
  };
76
- return F ? /* @__PURE__ */ D("div", {
89
+ return M ? /* @__PURE__ */ T("div", {
77
90
  className: "space-y-6 p-6",
78
91
  "data-testid": "satisfaction-page",
79
92
  children: [
80
- /* @__PURE__ */ D("div", {
93
+ /* @__PURE__ */ T("div", {
81
94
  className: "flex flex-wrap items-start justify-between gap-4",
82
- children: [/* @__PURE__ */ D("div", {
95
+ children: [/* @__PURE__ */ T("div", {
83
96
  className: "max-w-3xl",
84
- children: [/* @__PURE__ */ D("h1", {
97
+ children: [/* @__PURE__ */ T("h1", {
85
98
  className: "flex items-center gap-2 text-2xl font-semibold",
86
- children: [/* @__PURE__ */ E(C, {
99
+ children: [/* @__PURE__ */ w(ne, {
87
100
  className: "h-6 w-6",
88
101
  "aria-hidden": !0
89
- }), P("planmagnet.satisfaction.title", "Customer satisfaction")]
90
- }), /* @__PURE__ */ E("p", {
102
+ }), j("planmagnet.satisfaction.title", "Customer satisfaction")]
103
+ }), /* @__PURE__ */ w("p", {
91
104
  className: "mt-1 text-sm text-muted-foreground",
92
- children: P("planmagnet.satisfaction.subtitle", "CSAT and NPS from what customers said about your products, releases and projects. Responses are entered by your team or imported from a CSV file.")
105
+ children: j("planmagnet.satisfaction.subtitle", "CSAT and NPS from what customers said about your products, releases and projects. Responses are entered by your team or imported from a CSV file.")
93
106
  })]
94
- }), I.canCreate && /* @__PURE__ */ D("div", {
107
+ }), N.canCreate && /* @__PURE__ */ T("div", {
95
108
  className: "flex flex-wrap items-center gap-2",
96
- children: [/* @__PURE__ */ E(p, {
109
+ children: [/* @__PURE__ */ w(p, {
97
110
  links: J,
98
111
  onImported: (e) => W({
99
112
  tone: "status",
100
- message: P("planmagnet.satisfaction.notice.imported", "Import done — new responses: {{created}}; already imported and skipped: {{skipped}}.", {
113
+ message: j("planmagnet.satisfaction.notice.imported", "Import done — new responses: {{created}}; already imported and skipped: {{skipped}}.", {
101
114
  created: e.createdCount,
102
115
  skipped: e.skippedCount
103
116
  })
104
117
  })
105
- }), /* @__PURE__ */ E(m, {
118
+ }), /* @__PURE__ */ w(m, {
106
119
  links: J,
107
120
  onSaved: () => W({
108
121
  tone: "status",
109
- message: P("planmagnet.satisfaction.notice.created", "The response was added.")
122
+ message: j("planmagnet.satisfaction.notice.created", "The response was added.")
110
123
  })
111
124
  })]
112
125
  })]
113
126
  }),
114
- I.readOnly && /* @__PURE__ */ D("p", {
127
+ N.readOnly && /* @__PURE__ */ T("p", {
115
128
  className: "flex items-center gap-1.5 text-xs text-muted-foreground",
116
129
  "data-testid": "satisfaction-read-only",
117
- children: [/* @__PURE__ */ E(x, {
130
+ children: [/* @__PURE__ */ w(S, {
118
131
  className: "h-3.5 w-3.5 shrink-0",
119
132
  "aria-hidden": !0
120
- }), P("planmagnet.satisfaction.readOnly", "You can read customer satisfaction. Adding, importing, editing and deleting responses is for product managers.")]
133
+ }), j("planmagnet.satisfaction.readOnly", "You can read customer satisfaction. Adding, importing, editing and deleting responses is for product managers.")]
121
134
  }),
122
- /* @__PURE__ */ D("div", {
135
+ /* @__PURE__ */ T("div", {
123
136
  className: "space-y-3 rounded-lg border p-3",
124
137
  "data-testid": "satisfaction-filters",
125
- children: [/* @__PURE__ */ E(g, {
138
+ children: [/* @__PURE__ */ w(g, {
126
139
  value: z.window,
127
140
  onChange: (e) => G({ window: e }),
128
141
  current: $?.window ?? null
129
- }), /* @__PURE__ */ D("div", {
142
+ }), /* @__PURE__ */ T("div", {
130
143
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
131
- children: [/* @__PURE__ */ D("div", {
144
+ children: [/* @__PURE__ */ T("div", {
132
145
  className: "grid flex-1 gap-2 sm:grid-cols-3",
133
146
  children: [
134
- /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
147
+ /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("label", {
135
148
  htmlFor: "satisfaction-filter-product",
136
149
  className: "sr-only",
137
- children: P("planmagnet.satisfaction.filters.product", "Product")
138
- }), /* @__PURE__ */ E(ee, {
150
+ children: j("planmagnet.satisfaction.filters.product", "Product")
151
+ }), /* @__PURE__ */ w(l, {
139
152
  id: "satisfaction-filter-product",
140
153
  value: z.productId,
141
- noneLabel: P("planmagnet.satisfaction.filters.allProducts", "All products"),
154
+ noneLabel: j("planmagnet.satisfaction.filters.allProducts", "All products"),
142
155
  onChange: (e) => {
143
- let t = R.find((e) => e.id === z.releaseId);
156
+ let t = F.find((e) => e.id === z.releaseId);
144
157
  G({
145
158
  productId: e,
146
159
  releaseId: !t || !t.productId || !e || t.productId === e ? z.releaseId : ""
147
160
  });
148
161
  }
149
162
  })] }),
150
- /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
163
+ /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("label", {
151
164
  htmlFor: "satisfaction-filter-release",
152
165
  className: "sr-only",
153
- children: P("planmagnet.satisfaction.filters.release", "Release")
154
- }), /* @__PURE__ */ E(d, {
166
+ children: j("planmagnet.satisfaction.filters.release", "Release")
167
+ }), /* @__PURE__ */ w(d, {
155
168
  id: "satisfaction-filter-release",
156
169
  value: z.releaseId,
157
170
  productId: z.productId,
158
- noneLabel: P("planmagnet.satisfaction.filters.allReleases", "All releases"),
171
+ noneLabel: j("planmagnet.satisfaction.filters.allReleases", "All releases"),
159
172
  onChange: (e) => G({ releaseId: e })
160
173
  })] }),
161
- /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("label", {
174
+ /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("label", {
162
175
  htmlFor: "satisfaction-filter-project",
163
176
  className: "sr-only",
164
- children: P("planmagnet.satisfaction.filters.project", "Project")
165
- }), /* @__PURE__ */ E(te, {
177
+ children: j("planmagnet.satisfaction.filters.project", "Project")
178
+ }), /* @__PURE__ */ w(te, {
166
179
  id: "satisfaction-filter-project",
167
180
  value: z.projectId,
168
- noneLabel: P("planmagnet.satisfaction.filters.allProjects", "All projects"),
181
+ noneLabel: j("planmagnet.satisfaction.filters.allProjects", "All projects"),
169
182
  onChange: (e) => G({ projectId: e })
170
183
  })] })
171
184
  ]
172
- }), Y && /* @__PURE__ */ D(O, {
185
+ }), Y && /* @__PURE__ */ T(ce, {
173
186
  size: "sm",
174
187
  variant: "ghost",
175
188
  onClick: () => G({
@@ -178,72 +191,72 @@ function P() {
178
191
  projectId: ""
179
192
  }),
180
193
  "data-testid": "satisfaction-filters-clear",
181
- children: [/* @__PURE__ */ E(ne, { className: "mr-1 h-4 w-4" }), P("planmagnet.satisfaction.filters.clear", "Clear filters")]
194
+ children: [/* @__PURE__ */ w(re, { className: "mr-1 h-4 w-4" }), j("planmagnet.satisfaction.filters.clear", "Clear filters")]
182
195
  })]
183
196
  })]
184
197
  }),
185
- z.productId && /* @__PURE__ */ E("p", {
198
+ z.productId && /* @__PURE__ */ w("p", {
186
199
  className: "-mt-3 text-xs text-muted-foreground",
187
- children: P("planmagnet.satisfaction.filters.productNote", "A product counts its own responses and those about its releases that name no product of their own.")
200
+ children: j("planmagnet.satisfaction.filters.productNote", "A product counts its own responses and those about its releases that name no product of their own.")
188
201
  }),
189
- U && /* @__PURE__ */ D("div", {
202
+ U && /* @__PURE__ */ T("div", {
190
203
  role: U.tone,
191
204
  className: `flex items-start justify-between gap-3 rounded-md p-3 text-sm ${U.tone === "alert" ? "bg-status-error-bg-subtle text-status-error-text" : "bg-muted"}`,
192
205
  "data-testid": "satisfaction-notice",
193
- children: [/* @__PURE__ */ E("span", { children: U.message }), /* @__PURE__ */ E("button", {
206
+ children: [/* @__PURE__ */ w("span", { children: U.message }), /* @__PURE__ */ w("button", {
194
207
  type: "button",
195
208
  className: "shrink-0 text-xs text-muted-foreground hover:underline",
196
209
  onClick: () => W(null),
197
- children: P("planmagnet.satisfaction.dismiss", "Dismiss")
210
+ children: j("planmagnet.satisfaction.dismiss", "Dismiss")
198
211
  })]
199
212
  }),
200
- Z && !$ ? /* @__PURE__ */ D("div", {
213
+ Z && !$ ? /* @__PURE__ */ T("div", {
201
214
  className: "flex items-center gap-2 text-sm text-muted-foreground",
202
- children: [/* @__PURE__ */ E(S, { className: "h-4 w-4 animate-spin" }), P("planmagnet.satisfaction.loading", "Loading…")]
203
- }) : Q && !$ ? /* @__PURE__ */ E(r, { message: n(Q) ? P("planmagnet.satisfaction.errors.readForbidden", "Your role cannot see customer satisfaction in this workspace.") : P("planmagnet.satisfaction.errors.load", "Customer satisfaction could not be loaded: {{message}}", { message: Q.message }) }) : $ ? /* @__PURE__ */ D(T, { children: [
204
- Q && /* @__PURE__ */ E(r, { message: P("planmagnet.satisfaction.errors.refresh", "The figures could not be refreshed: {{message}}", { message: Q.message }) }),
205
- se && /* @__PURE__ */ D("p", {
215
+ children: [/* @__PURE__ */ w(C, { className: "h-4 w-4 animate-spin" }), j("planmagnet.satisfaction.loading", "Loading…")]
216
+ }) : Q && !$ ? /* @__PURE__ */ w(r, { message: n(Q) ? j("planmagnet.satisfaction.errors.readForbidden", "Your role cannot see customer satisfaction in this workspace.") : j("planmagnet.satisfaction.errors.load", "Customer satisfaction could not be loaded: {{message}}", { message: Q.message }) }) : $ ? /* @__PURE__ */ T(se, { children: [
217
+ Q && /* @__PURE__ */ w(r, { message: j("planmagnet.satisfaction.errors.refresh", "The figures could not be refreshed: {{message}}", { message: Q.message }) }),
218
+ ue && /* @__PURE__ */ T("p", {
206
219
  className: "flex items-center gap-2 text-xs text-muted-foreground",
207
220
  role: "status",
208
- children: [/* @__PURE__ */ E(S, {
221
+ children: [/* @__PURE__ */ w(C, {
209
222
  className: "h-3.5 w-3.5 animate-spin",
210
223
  "aria-hidden": !0
211
- }), P("planmagnet.satisfaction.updating", "Updating the figures…")]
224
+ }), j("planmagnet.satisfaction.updating", "Updating the figures…")]
212
225
  }),
213
- /* @__PURE__ */ E(s, { summary: $ })
226
+ /* @__PURE__ */ w(o, { summary: $ })
214
227
  ] }) : null,
215
- /* @__PURE__ */ D(k, { children: [/* @__PURE__ */ D(j, {
228
+ /* @__PURE__ */ T(E, { children: [/* @__PURE__ */ T(O, {
216
229
  className: "flex flex-row flex-wrap items-center justify-between gap-2 space-y-0",
217
- children: [/* @__PURE__ */ E(M, { children: P("planmagnet.satisfaction.list.title", "Responses") }), /* @__PURE__ */ E("div", {
230
+ children: [/* @__PURE__ */ w(k, { children: j("planmagnet.satisfaction.list.title", "Responses") }), /* @__PURE__ */ w("div", {
218
231
  className: "flex items-center gap-1 rounded-md border p-0.5",
219
232
  role: "group",
220
- "aria-label": P("planmagnet.satisfaction.list.metricFilter", "Metric"),
221
- children: ["", ...i].map((e) => /* @__PURE__ */ E("button", {
233
+ "aria-label": j("planmagnet.satisfaction.list.metricFilter", "Metric"),
234
+ children: ["", ...ee].map((e) => /* @__PURE__ */ w("button", {
222
235
  type: "button",
223
236
  "aria-pressed": z.metricType === e,
224
237
  onClick: () => G({ metricType: e }),
225
238
  "data-testid": `satisfaction-metric-${e || "ALL"}`,
226
239
  className: `rounded px-2.5 py-1 text-xs ${z.metricType === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
227
- children: e || P("planmagnet.satisfaction.list.allMetrics", "All")
240
+ children: e || j("planmagnet.satisfaction.list.allMetrics", "All")
228
241
  }, e || "all"))
229
242
  })]
230
- }), /* @__PURE__ */ E(A, { children: /* @__PURE__ */ E(h, {
231
- workspaceId: F,
243
+ }), /* @__PURE__ */ w(D, { children: /* @__PURE__ */ w(h, {
244
+ workspaceId: M,
232
245
  filter: q,
233
- access: I,
246
+ access: N,
234
247
  links: J,
235
248
  filtered: Y || !!z.metricType,
236
249
  onNotice: (e, t) => W({
237
250
  message: e,
238
251
  tone: t
239
252
  }),
240
- onStale: ce
253
+ onStale: de
241
254
  }, JSON.stringify(q)) })] })
242
255
  ]
243
- }) : /* @__PURE__ */ E("div", {
256
+ }) : /* @__PURE__ */ w("div", {
244
257
  className: "p-6 text-sm text-muted-foreground",
245
- children: P("planmagnet.satisfaction.noWorkspace", "Choose a workspace to see customer satisfaction.")
258
+ children: j("planmagnet.satisfaction.noWorkspace", "Choose a workspace to see customer satisfaction.")
246
259
  });
247
260
  }
248
261
  //#endregion
249
- export { P as ProductsCustomerSatisfactionPagePage };
262
+ export { j as ProductsCustomerSatisfactionPagePage };
@@ -124,7 +124,8 @@ export interface SatisfactionPageState {
124
124
  /** The Customer satisfaction page for a scope (and a window other than the default), as a navigate() path. */
125
125
  export declare function satisfactionPageLink(scope: SatisfactionScope, window?: WindowChoice): string;
126
126
  /**
127
- * The page state a URL query asks for (`?window=&from=&to=&product=&release=&project=&metric=`). Anything unreadable
127
+ * The page state a URL query asks for (`?window=&from=&to=&product=&release=&satisfactionProject=&metric=`).
128
+ * `project` belongs to the host's active context; it must never silently filter this page. Anything unreadable
128
129
  * falls back to the default — the trailing 90 days, every product, release and project, both metrics.
129
130
  */
130
131
  export declare function pageStateFromSearch(search: string, now: Date): SatisfactionPageState;
@@ -107,7 +107,7 @@ function j(e, t) {
107
107
  }
108
108
  function M(e, t) {
109
109
  let r = new URLSearchParams();
110
- t && t.preset !== "LAST_90_DAYS" && (r.set("window", t.preset), t.preset === "CUSTOM" && (r.set("from", t.from), r.set("to", t.to))), e.productId && r.set("product", e.productId), e.releaseId && r.set("release", e.releaseId), e.projectId && r.set("project", e.projectId);
110
+ t && t.preset !== "LAST_90_DAYS" && (r.set("window", t.preset), t.preset === "CUSTOM" && (r.set("from", t.from), r.set("to", t.to))), e.productId && r.set("product", e.productId), e.releaseId && r.set("release", e.releaseId), e.projectId && r.set("satisfactionProject", e.projectId);
111
111
  let i = r.toString();
112
112
  return `/${n}${i ? `?${i}` : ""}`;
113
113
  }
@@ -134,7 +134,7 @@ function N(e, t) {
134
134
  window: s,
135
135
  productId: n.get("product") ?? "",
136
136
  releaseId: n.get("release") ?? "",
137
- projectId: n.get("project") ?? "",
137
+ projectId: n.get("satisfactionProject") ?? "",
138
138
  metricType: a(c) ? c : ""
139
139
  };
140
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.1008.3",
3
+ "version": "2026.1008.4",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [