@burdenoff/microfe-planmagnet 2026.928.9 → 2026.928.10

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.
Files changed (30) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +1 -1
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +217 -209
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/pages/ProductsCreateProductPagePage.d.ts.map +1 -1
  8. package/dist/planmagnet/pages/ProductsCreateProductPagePage.js +198 -99
  9. package/dist/planmagnet/pages/ProductsCreateProductPagePage.js.map +1 -1
  10. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  11. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +260 -170
  12. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  13. package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts +2 -0
  14. package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts.map +1 -0
  15. package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js +283 -0
  16. package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js.map +1 -0
  17. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  18. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +166 -246
  19. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  20. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  21. package/dist/planmagnet/pages/ProductsProductsPagePage.js +147 -117
  22. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  23. package/dist/planmagnet/product/ProductControls.d.ts +52 -0
  24. package/dist/planmagnet/product/ProductControls.d.ts.map +1 -0
  25. package/dist/planmagnet/product/ProductControls.js +780 -0
  26. package/dist/planmagnet/product/ProductControls.js.map +1 -0
  27. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
  28. package/dist/planmagnet/roadmaps/RoadmapControls.js +1 -1
  29. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
  30. package/package.json +1 -1
@@ -0,0 +1,780 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { LEAVES_PAGE_CONTEXT as n, throwOnError as r } from "../utils/mutationResult.js";
3
+ import { ConfirmButton as i, ErrorLine as a } from "../controls/ConfirmButton.js";
4
+ import { enumLabel as o } from "../controls/WorkItemControls.js";
5
+ import { detailPath as s } from "../utils/rowNavigation.js";
6
+ import { useState as c } from "react";
7
+ import { Archive as l, ArchiveRestore as u, Plus as d, Trash2 as f, X as p } from "lucide-react";
8
+ import { gql as m } from "@apollo/client";
9
+ import { useMutation as h, useQuery as g } from "@apollo/client/react";
10
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
11
+ import { Badge as y, Button as b, Checkbox as x, ResponsiveDialog as S, ResponsiveDialogContent as C, ResponsiveDialogDescription as w, ResponsiveDialogFooter as T, ResponsiveDialogHeader as E, ResponsiveDialogTitle as D, ResponsiveDialogTrigger as O } from "@burdenoff/fe-libs/ui";
12
+ //#region src/planmagnet/product/ProductControls.tsx
13
+ var k = [
14
+ "GetProducts",
15
+ "GetProductForEdit",
16
+ "GetProductDetail"
17
+ ], A = m`
18
+ mutation ProductControlsDelete($id: ID!) {
19
+ planmagnetDeleteProduct(id: $id)
20
+ }
21
+ `, j = m`
22
+ mutation ProductControlsArchive($id: ID!) {
23
+ planmagnetArchiveProduct(id: $id) {
24
+ id
25
+ archivedAt
26
+ }
27
+ }
28
+ `, M = m`
29
+ mutation ProductControlsUnarchive($id: ID!) {
30
+ planmagnetUnarchiveProduct(id: $id) {
31
+ id
32
+ archivedAt
33
+ }
34
+ }
35
+ `;
36
+ function N({ productId: e, name: o, archivedAt: s }) {
37
+ let d = t(), [p, m] = h(A, {
38
+ refetchQueries: k,
39
+ context: n
40
+ }), [g, y] = h(j, {
41
+ refetchQueries: k,
42
+ awaitRefetchQueries: !0
43
+ }), [x, S] = h(M, {
44
+ refetchQueries: k,
45
+ awaitRefetchQueries: !0
46
+ }), [C, w] = c(null), T = async () => {
47
+ w(null);
48
+ try {
49
+ await r(p({ variables: { id: e } })), d("/products/products-page");
50
+ } catch (e) {
51
+ w(e instanceof Error ? e.message : "The product could not be deleted.");
52
+ }
53
+ }, E = async () => {
54
+ w(null);
55
+ try {
56
+ s ? await r(x({ variables: { id: e } })) : await r(g({ variables: { id: e } }));
57
+ } catch (e) {
58
+ w(e instanceof Error ? e.message : "The product could not be updated.");
59
+ }
60
+ };
61
+ return /* @__PURE__ */ v("div", {
62
+ className: "flex flex-col items-end gap-2",
63
+ children: [/* @__PURE__ */ v("div", {
64
+ className: "flex gap-2",
65
+ children: [/* @__PURE__ */ v(b, {
66
+ size: "sm",
67
+ variant: "outline",
68
+ "data-testid": "product-toggle-archive",
69
+ disabled: y.loading || S.loading,
70
+ onClick: () => void E(),
71
+ children: [_(s ? u : l, { className: "mr-1 h-4 w-4" }), s ? "Unarchive" : "Archive"]
72
+ }), /* @__PURE__ */ _(i, {
73
+ label: "Delete",
74
+ variant: "outline",
75
+ icon: /* @__PURE__ */ _(f, { className: "mr-1 h-4 w-4" }),
76
+ title: `Delete ${o}?`,
77
+ description: "Every linked roadmap, release and feature request stays intact — only its link to this product is cleared. This cannot be undone here.",
78
+ confirmLabel: "Delete product",
79
+ destructive: !0,
80
+ disabled: m.loading,
81
+ onConfirm: () => void T()
82
+ })]
83
+ }), C && /* @__PURE__ */ _(a, { message: C })]
84
+ });
85
+ }
86
+ var P = m`
87
+ mutation ProductControlsLinkRoadmap($productId: ID!, $roadmapId: ID!) {
88
+ planmagnetLinkProductRoadmap(productId: $productId, roadmapId: $roadmapId) {
89
+ id
90
+ }
91
+ }
92
+ `, F = m`
93
+ mutation ProductControlsUnlinkRoadmap($productId: ID!, $roadmapId: ID!) {
94
+ planmagnetUnlinkProductRoadmap(productId: $productId, roadmapId: $roadmapId) {
95
+ id
96
+ }
97
+ }
98
+ `, I = m`
99
+ query ProductControlsCandidateRoadmaps($workspaceId: ID!) {
100
+ planmagnetRoadmaps(workspaceId: $workspaceId, pagination: { first: 100 }) {
101
+ items {
102
+ id
103
+ name
104
+ }
105
+ totalCount
106
+ }
107
+ }
108
+ `;
109
+ function L({ productId: e, roadmaps: n }) {
110
+ let i = t(), [o, l] = h(F, {
111
+ refetchQueries: k,
112
+ awaitRefetchQueries: !0
113
+ }), [u, d] = c(null), [f, m] = c(null), g = async (t) => {
114
+ d(null), m(t);
115
+ try {
116
+ await r(o({ variables: {
117
+ productId: e,
118
+ roadmapId: t
119
+ } }));
120
+ } catch (e) {
121
+ d(e instanceof Error ? e.message : "The roadmap could not be unlinked.");
122
+ } finally {
123
+ m(null);
124
+ }
125
+ };
126
+ return /* @__PURE__ */ v("div", {
127
+ className: "space-y-3",
128
+ "data-testid": "product-roadmaps",
129
+ children: [
130
+ /* @__PURE__ */ v("div", {
131
+ className: "flex items-center justify-between",
132
+ children: [/* @__PURE__ */ v("p", {
133
+ className: "text-sm text-muted-foreground",
134
+ children: [
135
+ n.length,
136
+ " roadmap",
137
+ n.length === 1 ? "" : "s"
138
+ ]
139
+ }), /* @__PURE__ */ _(R, {
140
+ productId: e,
141
+ existingIds: n.map((e) => e.id)
142
+ })]
143
+ }),
144
+ u && /* @__PURE__ */ _(a, { message: u }),
145
+ n.length === 0 ? /* @__PURE__ */ _("p", {
146
+ className: "text-sm text-muted-foreground text-center py-4",
147
+ children: "No roadmaps linked to this product yet."
148
+ }) : /* @__PURE__ */ _("ul", {
149
+ className: "space-y-1",
150
+ children: n.map((e) => /* @__PURE__ */ v("li", {
151
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
152
+ children: [/* @__PURE__ */ _("button", {
153
+ type: "button",
154
+ className: "min-w-0 truncate text-left hover:underline",
155
+ onClick: () => i(s("/products/roadmap-detail-page", e.id)),
156
+ children: e.name
157
+ }), /* @__PURE__ */ _(b, {
158
+ size: "sm",
159
+ variant: "ghost",
160
+ "aria-label": `Unlink ${e.name} from product`,
161
+ disabled: l.loading,
162
+ onClick: () => void g(e.id),
163
+ children: f === e.id ? "…" : /* @__PURE__ */ _(p, { className: "h-4 w-4" })
164
+ })]
165
+ }, e.id))
166
+ })
167
+ ]
168
+ });
169
+ }
170
+ function R({ productId: t, existingIds: n }) {
171
+ let { workspaceId: i } = e(), [o, s] = c(!1), [l, u] = c(/* @__PURE__ */ new Set()), [f, p] = c(null), { data: m, loading: y } = g(I, {
172
+ variables: { workspaceId: i ?? "" },
173
+ skip: !o || !i
174
+ }), A = (m?.planmagnetRoadmaps.items ?? []).filter((e) => !n.includes(e.id)), [j, M] = h(P, {
175
+ refetchQueries: k,
176
+ awaitRefetchQueries: !0
177
+ }), N = (e, t) => u((n) => {
178
+ let r = new Set(n);
179
+ return t ? r.add(e) : r.delete(e), r;
180
+ }), F = () => {
181
+ u(/* @__PURE__ */ new Set()), p(null);
182
+ }, L = async () => {
183
+ p(null);
184
+ try {
185
+ await Promise.all([...l].map((e) => r(j({ variables: {
186
+ productId: t,
187
+ roadmapId: e
188
+ } })))), F(), s(!1);
189
+ } catch (e) {
190
+ p(e instanceof Error ? e.message : "The roadmaps could not be linked.");
191
+ }
192
+ };
193
+ return /* @__PURE__ */ v(S, {
194
+ open: o,
195
+ onOpenChange: (e) => {
196
+ s(e), e || F();
197
+ },
198
+ children: [/* @__PURE__ */ _(O, {
199
+ asChild: !0,
200
+ children: /* @__PURE__ */ v(b, {
201
+ size: "sm",
202
+ variant: "outline",
203
+ "data-testid": "product-add-roadmaps",
204
+ children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Add roadmap"]
205
+ })
206
+ }), /* @__PURE__ */ v(C, {
207
+ className: "max-w-lg",
208
+ children: [
209
+ /* @__PURE__ */ v(E, { children: [/* @__PURE__ */ _(D, { children: "Link roadmaps" }), /* @__PURE__ */ _(w, { children: "Linking a roadmap does not move or copy it — it stays exactly where it is, now also reachable from this product." })] }),
210
+ /* @__PURE__ */ v("div", {
211
+ className: "max-h-80 overflow-y-auto space-y-1",
212
+ "data-testid": "product-add-roadmap-candidates",
213
+ children: [
214
+ y && /* @__PURE__ */ _("p", {
215
+ className: "text-sm text-muted-foreground py-4 text-center",
216
+ children: "Loading…"
217
+ }),
218
+ !y && A.length === 0 && /* @__PURE__ */ _("p", {
219
+ className: "text-sm text-muted-foreground py-4 text-center",
220
+ children: "Every roadmap in this workspace is already linked to this product."
221
+ }),
222
+ A.map((e) => {
223
+ let t = `product-add-roadmap-${e.id}`;
224
+ return /* @__PURE__ */ v("label", {
225
+ htmlFor: t,
226
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
227
+ children: [/* @__PURE__ */ _(x, {
228
+ id: t,
229
+ checked: l.has(e.id),
230
+ onCheckedChange: (t) => N(e.id, t === !0)
231
+ }), /* @__PURE__ */ _("span", {
232
+ className: "min-w-0 flex-1 truncate",
233
+ children: e.name
234
+ })]
235
+ }, e.id);
236
+ })
237
+ ]
238
+ }),
239
+ f && /* @__PURE__ */ _(a, { message: f }),
240
+ /* @__PURE__ */ _(T, { children: /* @__PURE__ */ _(b, {
241
+ onClick: () => void L(),
242
+ disabled: l.size === 0 || M.loading,
243
+ "data-testid": "product-add-roadmaps-confirm",
244
+ children: M.loading ? "Linking…" : `Link ${l.size} roadmap${l.size === 1 ? "" : "s"}`
245
+ }) })
246
+ ]
247
+ })]
248
+ });
249
+ }
250
+ var z = m`
251
+ mutation ProductControlsLinkRelease($productId: ID!, $releaseId: ID!) {
252
+ planmagnetLinkProductRelease(productId: $productId, releaseId: $releaseId) {
253
+ id
254
+ }
255
+ }
256
+ `, B = m`
257
+ mutation ProductControlsUnlinkRelease($productId: ID!, $releaseId: ID!) {
258
+ planmagnetUnlinkProductRelease(productId: $productId, releaseId: $releaseId) {
259
+ id
260
+ }
261
+ }
262
+ `, V = m`
263
+ query ProductControlsCandidateReleases($workspaceId: ID!) {
264
+ planmagnetReleases(workspaceId: $workspaceId, pagination: { first: 100 }) {
265
+ items {
266
+ id
267
+ name
268
+ version
269
+ status
270
+ }
271
+ totalCount
272
+ }
273
+ }
274
+ `;
275
+ function H({ productId: e, releases: n }) {
276
+ let i = t(), [l, u] = h(B, {
277
+ refetchQueries: k,
278
+ awaitRefetchQueries: !0
279
+ }), [d, f] = c(null), [m, g] = c(null), x = async (t) => {
280
+ f(null), g(t);
281
+ try {
282
+ await r(l({ variables: {
283
+ productId: e,
284
+ releaseId: t
285
+ } }));
286
+ } catch (e) {
287
+ f(e instanceof Error ? e.message : "The release could not be unlinked.");
288
+ } finally {
289
+ g(null);
290
+ }
291
+ };
292
+ return /* @__PURE__ */ v("div", {
293
+ className: "space-y-3",
294
+ "data-testid": "product-releases",
295
+ children: [
296
+ /* @__PURE__ */ v("div", {
297
+ className: "flex items-center justify-between",
298
+ children: [/* @__PURE__ */ v("p", {
299
+ className: "text-sm text-muted-foreground",
300
+ children: [
301
+ n.length,
302
+ " release",
303
+ n.length === 1 ? "" : "s"
304
+ ]
305
+ }), /* @__PURE__ */ _(U, {
306
+ productId: e,
307
+ existingIds: n.map((e) => e.id)
308
+ })]
309
+ }),
310
+ d && /* @__PURE__ */ _(a, { message: d }),
311
+ n.length === 0 ? /* @__PURE__ */ _("p", {
312
+ className: "text-sm text-muted-foreground text-center py-4",
313
+ children: "No releases linked to this product yet."
314
+ }) : /* @__PURE__ */ _("ul", {
315
+ className: "space-y-1",
316
+ children: n.map((e) => /* @__PURE__ */ v("li", {
317
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
318
+ children: [/* @__PURE__ */ v("button", {
319
+ type: "button",
320
+ className: "min-w-0 truncate text-left hover:underline",
321
+ onClick: () => i(s("/products/release-detail-page", e.id)),
322
+ children: [/* @__PURE__ */ _("span", {
323
+ className: "text-muted-foreground mr-2",
324
+ children: e.version
325
+ }), e.name]
326
+ }), /* @__PURE__ */ v("div", {
327
+ className: "flex items-center gap-2 shrink-0",
328
+ children: [/* @__PURE__ */ _(y, {
329
+ variant: "outline",
330
+ className: "text-xs capitalize",
331
+ children: o(e.status)
332
+ }), /* @__PURE__ */ _(b, {
333
+ size: "sm",
334
+ variant: "ghost",
335
+ "aria-label": `Unlink ${e.name} from product`,
336
+ disabled: u.loading,
337
+ onClick: () => void x(e.id),
338
+ children: m === e.id ? "…" : /* @__PURE__ */ _(p, { className: "h-4 w-4" })
339
+ })]
340
+ })]
341
+ }, e.id))
342
+ })
343
+ ]
344
+ });
345
+ }
346
+ function U({ productId: t, existingIds: n }) {
347
+ let { workspaceId: i } = e(), [o, s] = c(!1), [l, u] = c(/* @__PURE__ */ new Set()), [f, p] = c(null), { data: m, loading: y } = g(V, {
348
+ variables: { workspaceId: i ?? "" },
349
+ skip: !o || !i
350
+ }), A = (m?.planmagnetReleases.items ?? []).filter((e) => !n.includes(e.id)), [j, M] = h(z, {
351
+ refetchQueries: k,
352
+ awaitRefetchQueries: !0
353
+ }), N = (e, t) => u((n) => {
354
+ let r = new Set(n);
355
+ return t ? r.add(e) : r.delete(e), r;
356
+ }), P = () => {
357
+ u(/* @__PURE__ */ new Set()), p(null);
358
+ }, F = async () => {
359
+ p(null);
360
+ try {
361
+ await Promise.all([...l].map((e) => r(j({ variables: {
362
+ productId: t,
363
+ releaseId: e
364
+ } })))), P(), s(!1);
365
+ } catch (e) {
366
+ p(e instanceof Error ? e.message : "The releases could not be linked.");
367
+ }
368
+ };
369
+ return /* @__PURE__ */ v(S, {
370
+ open: o,
371
+ onOpenChange: (e) => {
372
+ s(e), e || P();
373
+ },
374
+ children: [/* @__PURE__ */ _(O, {
375
+ asChild: !0,
376
+ children: /* @__PURE__ */ v(b, {
377
+ size: "sm",
378
+ variant: "outline",
379
+ "data-testid": "product-add-releases",
380
+ children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Add release"]
381
+ })
382
+ }), /* @__PURE__ */ v(C, {
383
+ className: "max-w-lg",
384
+ children: [
385
+ /* @__PURE__ */ v(E, { children: [/* @__PURE__ */ _(D, { children: "Link releases" }), /* @__PURE__ */ _(w, { children: "Linking a release does not move or copy it — it stays exactly where it is, now also reachable from this product." })] }),
386
+ /* @__PURE__ */ v("div", {
387
+ className: "max-h-80 overflow-y-auto space-y-1",
388
+ "data-testid": "product-add-release-candidates",
389
+ children: [
390
+ y && /* @__PURE__ */ _("p", {
391
+ className: "text-sm text-muted-foreground py-4 text-center",
392
+ children: "Loading…"
393
+ }),
394
+ !y && A.length === 0 && /* @__PURE__ */ _("p", {
395
+ className: "text-sm text-muted-foreground py-4 text-center",
396
+ children: "Every release in this workspace is already linked to this product."
397
+ }),
398
+ A.map((e) => {
399
+ let t = `product-add-release-${e.id}`;
400
+ return /* @__PURE__ */ v("label", {
401
+ htmlFor: t,
402
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
403
+ children: [/* @__PURE__ */ _(x, {
404
+ id: t,
405
+ checked: l.has(e.id),
406
+ onCheckedChange: (t) => N(e.id, t === !0)
407
+ }), /* @__PURE__ */ v("span", {
408
+ className: "min-w-0 flex-1 truncate",
409
+ children: [/* @__PURE__ */ _("span", {
410
+ className: "text-muted-foreground mr-2",
411
+ children: e.version
412
+ }), e.name]
413
+ })]
414
+ }, e.id);
415
+ })
416
+ ]
417
+ }),
418
+ f && /* @__PURE__ */ _(a, { message: f }),
419
+ /* @__PURE__ */ _(T, { children: /* @__PURE__ */ _(b, {
420
+ onClick: () => void F(),
421
+ disabled: l.size === 0 || M.loading,
422
+ "data-testid": "product-add-releases-confirm",
423
+ children: M.loading ? "Linking…" : `Link ${l.size} release${l.size === 1 ? "" : "s"}`
424
+ }) })
425
+ ]
426
+ })]
427
+ });
428
+ }
429
+ var W = m`
430
+ mutation ProductControlsLinkFeatureRequest($productId: ID!, $featureRequestId: ID!) {
431
+ planmagnetLinkProductFeatureRequest(
432
+ productId: $productId
433
+ featureRequestId: $featureRequestId
434
+ ) {
435
+ id
436
+ }
437
+ }
438
+ `, G = m`
439
+ mutation ProductControlsUnlinkFeatureRequest($productId: ID!, $featureRequestId: ID!) {
440
+ planmagnetUnlinkProductFeatureRequest(
441
+ productId: $productId
442
+ featureRequestId: $featureRequestId
443
+ ) {
444
+ id
445
+ }
446
+ }
447
+ `, K = m`
448
+ query ProductControlsCandidateFeatureRequests($workspaceId: ID!) {
449
+ planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: { first: 100 }) {
450
+ items {
451
+ id
452
+ title
453
+ status
454
+ }
455
+ totalCount
456
+ }
457
+ }
458
+ `;
459
+ function q({ productId: e, featureRequests: n }) {
460
+ let i = t(), [l, u] = h(G, {
461
+ refetchQueries: k,
462
+ awaitRefetchQueries: !0
463
+ }), [d, f] = c(null), [m, g] = c(null), x = async (t) => {
464
+ f(null), g(t);
465
+ try {
466
+ await r(l({ variables: {
467
+ productId: e,
468
+ featureRequestId: t
469
+ } }));
470
+ } catch (e) {
471
+ f(e instanceof Error ? e.message : "The feature request could not be unlinked.");
472
+ } finally {
473
+ g(null);
474
+ }
475
+ };
476
+ return /* @__PURE__ */ v("div", {
477
+ className: "space-y-3",
478
+ "data-testid": "product-feature-requests",
479
+ children: [
480
+ /* @__PURE__ */ v("div", {
481
+ className: "flex items-center justify-between",
482
+ children: [/* @__PURE__ */ v("p", {
483
+ className: "text-sm text-muted-foreground",
484
+ children: [
485
+ n.length,
486
+ " feature request",
487
+ n.length === 1 ? "" : "s"
488
+ ]
489
+ }), /* @__PURE__ */ _(J, {
490
+ productId: e,
491
+ existingIds: n.map((e) => e.id)
492
+ })]
493
+ }),
494
+ d && /* @__PURE__ */ _(a, { message: d }),
495
+ n.length === 0 ? /* @__PURE__ */ _("p", {
496
+ className: "text-sm text-muted-foreground text-center py-4",
497
+ children: "No feature requests linked to this product yet."
498
+ }) : /* @__PURE__ */ _("ul", {
499
+ className: "space-y-1",
500
+ children: n.map((e) => /* @__PURE__ */ v("li", {
501
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
502
+ children: [/* @__PURE__ */ _("button", {
503
+ type: "button",
504
+ className: "min-w-0 truncate text-left hover:underline",
505
+ onClick: () => i(s("/products/feature-detail-page", e.id)),
506
+ children: e.title
507
+ }), /* @__PURE__ */ v("div", {
508
+ className: "flex items-center gap-2 shrink-0",
509
+ children: [/* @__PURE__ */ _(y, {
510
+ variant: "secondary",
511
+ className: "text-xs capitalize",
512
+ children: o(e.status)
513
+ }), /* @__PURE__ */ _(b, {
514
+ size: "sm",
515
+ variant: "ghost",
516
+ "aria-label": `Unlink ${e.title} from product`,
517
+ disabled: u.loading,
518
+ onClick: () => void x(e.id),
519
+ children: m === e.id ? "…" : /* @__PURE__ */ _(p, { className: "h-4 w-4" })
520
+ })]
521
+ })]
522
+ }, e.id))
523
+ })
524
+ ]
525
+ });
526
+ }
527
+ function J({ productId: t, existingIds: n }) {
528
+ let { workspaceId: i } = e(), [o, s] = c(!1), [l, u] = c(/* @__PURE__ */ new Set()), [f, p] = c(null), { data: m, loading: y } = g(K, {
529
+ variables: { workspaceId: i ?? "" },
530
+ skip: !o || !i
531
+ }), A = (m?.planmagnetFeatureRequests.items ?? []).filter((e) => !n.includes(e.id)), [j, M] = h(W, {
532
+ refetchQueries: k,
533
+ awaitRefetchQueries: !0
534
+ }), N = (e, t) => u((n) => {
535
+ let r = new Set(n);
536
+ return t ? r.add(e) : r.delete(e), r;
537
+ }), P = () => {
538
+ u(/* @__PURE__ */ new Set()), p(null);
539
+ }, F = async () => {
540
+ p(null);
541
+ try {
542
+ await Promise.all([...l].map((e) => r(j({ variables: {
543
+ productId: t,
544
+ featureRequestId: e
545
+ } })))), P(), s(!1);
546
+ } catch (e) {
547
+ p(e instanceof Error ? e.message : "The feature requests could not be linked.");
548
+ }
549
+ };
550
+ return /* @__PURE__ */ v(S, {
551
+ open: o,
552
+ onOpenChange: (e) => {
553
+ s(e), e || P();
554
+ },
555
+ children: [/* @__PURE__ */ _(O, {
556
+ asChild: !0,
557
+ children: /* @__PURE__ */ v(b, {
558
+ size: "sm",
559
+ variant: "outline",
560
+ "data-testid": "product-add-feature-requests",
561
+ children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Add feature request"]
562
+ })
563
+ }), /* @__PURE__ */ v(C, {
564
+ className: "max-w-lg",
565
+ children: [
566
+ /* @__PURE__ */ v(E, { children: [/* @__PURE__ */ _(D, { children: "Link feature requests" }), /* @__PURE__ */ _(w, { children: "Linking a feature request does not move or copy it — it stays exactly where it is, now also reachable from this product." })] }),
567
+ /* @__PURE__ */ v("div", {
568
+ className: "max-h-80 overflow-y-auto space-y-1",
569
+ "data-testid": "product-add-feature-request-candidates",
570
+ children: [
571
+ y && /* @__PURE__ */ _("p", {
572
+ className: "text-sm text-muted-foreground py-4 text-center",
573
+ children: "Loading…"
574
+ }),
575
+ !y && A.length === 0 && /* @__PURE__ */ _("p", {
576
+ className: "text-sm text-muted-foreground py-4 text-center",
577
+ children: "Every feature request in this workspace is already linked to this product."
578
+ }),
579
+ A.map((e) => {
580
+ let t = `product-add-feature-${e.id}`;
581
+ return /* @__PURE__ */ v("label", {
582
+ htmlFor: t,
583
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
584
+ children: [/* @__PURE__ */ _(x, {
585
+ id: t,
586
+ checked: l.has(e.id),
587
+ onCheckedChange: (t) => N(e.id, t === !0)
588
+ }), /* @__PURE__ */ _("span", {
589
+ className: "min-w-0 flex-1 truncate",
590
+ children: e.title
591
+ })]
592
+ }, e.id);
593
+ })
594
+ ]
595
+ }),
596
+ f && /* @__PURE__ */ _(a, { message: f }),
597
+ /* @__PURE__ */ _(T, { children: /* @__PURE__ */ _(b, {
598
+ onClick: () => void F(),
599
+ disabled: l.size === 0 || M.loading,
600
+ "data-testid": "product-add-feature-requests-confirm",
601
+ children: M.loading ? "Linking…" : `Link ${l.size} request${l.size === 1 ? "" : "s"}`
602
+ }) })
603
+ ]
604
+ })]
605
+ });
606
+ }
607
+ var Y = m`
608
+ mutation ProductControlsLinkObjective($productId: ID!, $objectiveId: ID!) {
609
+ planmagnetLinkProductObjective(productId: $productId, objectiveId: $objectiveId) {
610
+ id
611
+ }
612
+ }
613
+ `, X = m`
614
+ mutation ProductControlsUnlinkObjective($productId: ID!, $objectiveId: ID!) {
615
+ planmagnetUnlinkProductObjective(productId: $productId, objectiveId: $objectiveId)
616
+ }
617
+ `, Z = m`
618
+ query ProductControlsCandidateObjectives($workspaceId: ID!) {
619
+ planmagnetObjectives(workspaceId: $workspaceId, pagination: { first: 100 }) {
620
+ items {
621
+ id
622
+ title
623
+ status
624
+ }
625
+ totalCount
626
+ }
627
+ }
628
+ `;
629
+ function Q({ productId: e, objectives: n }) {
630
+ let i = t(), [s, l] = h(X, {
631
+ refetchQueries: k,
632
+ awaitRefetchQueries: !0
633
+ }), [u, d] = c(null), [f, m] = c(null), g = async (t) => {
634
+ d(null), m(t);
635
+ try {
636
+ await r(s({ variables: {
637
+ productId: e,
638
+ objectiveId: t
639
+ } }));
640
+ } catch (e) {
641
+ d(e instanceof Error ? e.message : "The objective could not be unlinked.");
642
+ } finally {
643
+ m(null);
644
+ }
645
+ };
646
+ return /* @__PURE__ */ v("div", {
647
+ className: "space-y-3",
648
+ "data-testid": "product-objectives",
649
+ children: [
650
+ /* @__PURE__ */ v("div", {
651
+ className: "flex items-center justify-between",
652
+ children: [/* @__PURE__ */ v("p", {
653
+ className: "text-sm text-muted-foreground",
654
+ children: [
655
+ n.length,
656
+ " objective",
657
+ n.length === 1 ? "" : "s"
658
+ ]
659
+ }), /* @__PURE__ */ _($, {
660
+ productId: e,
661
+ existingIds: n.map((e) => e.id)
662
+ })]
663
+ }),
664
+ u && /* @__PURE__ */ _(a, { message: u }),
665
+ n.length === 0 ? /* @__PURE__ */ _("p", {
666
+ className: "text-sm text-muted-foreground text-center py-4",
667
+ children: "No OKR objectives linked to this product yet."
668
+ }) : /* @__PURE__ */ _("ul", {
669
+ className: "space-y-1",
670
+ children: n.map((e) => /* @__PURE__ */ v("li", {
671
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
672
+ children: [/* @__PURE__ */ _("button", {
673
+ type: "button",
674
+ className: "min-w-0 truncate text-left hover:underline",
675
+ onClick: () => i(`/goals/objective-detail-page?objectiveId=${e.id}`),
676
+ children: e.title
677
+ }), /* @__PURE__ */ v("div", {
678
+ className: "flex items-center gap-2 shrink-0",
679
+ children: [/* @__PURE__ */ _(y, {
680
+ variant: "secondary",
681
+ className: "text-xs capitalize",
682
+ children: o(e.status)
683
+ }), /* @__PURE__ */ _(b, {
684
+ size: "sm",
685
+ variant: "ghost",
686
+ "aria-label": `Unlink ${e.title} from product`,
687
+ disabled: l.loading,
688
+ onClick: () => void g(e.id),
689
+ children: f === e.id ? "…" : /* @__PURE__ */ _(p, { className: "h-4 w-4" })
690
+ })]
691
+ })]
692
+ }, e.id))
693
+ })
694
+ ]
695
+ });
696
+ }
697
+ function $({ productId: t, existingIds: n }) {
698
+ let { workspaceId: i } = e(), [o, s] = c(!1), [l, u] = c(/* @__PURE__ */ new Set()), [f, p] = c(null), { data: m, loading: y } = g(Z, {
699
+ variables: { workspaceId: i ?? "" },
700
+ skip: !o || !i
701
+ }), A = (m?.planmagnetObjectives.items ?? []).filter((e) => !n.includes(e.id)), [j, M] = h(Y, {
702
+ refetchQueries: k,
703
+ awaitRefetchQueries: !0
704
+ }), N = (e, t) => u((n) => {
705
+ let r = new Set(n);
706
+ return t ? r.add(e) : r.delete(e), r;
707
+ }), P = () => {
708
+ u(/* @__PURE__ */ new Set()), p(null);
709
+ }, F = async () => {
710
+ p(null);
711
+ try {
712
+ await Promise.all([...l].map((e) => r(j({ variables: {
713
+ productId: t,
714
+ objectiveId: e
715
+ } })))), P(), s(!1);
716
+ } catch (e) {
717
+ p(e instanceof Error ? e.message : "The objectives could not be linked.");
718
+ }
719
+ };
720
+ return /* @__PURE__ */ v(S, {
721
+ open: o,
722
+ onOpenChange: (e) => {
723
+ s(e), e || P();
724
+ },
725
+ children: [/* @__PURE__ */ _(O, {
726
+ asChild: !0,
727
+ children: /* @__PURE__ */ v(b, {
728
+ size: "sm",
729
+ variant: "outline",
730
+ "data-testid": "product-add-objectives",
731
+ children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Add objective"]
732
+ })
733
+ }), /* @__PURE__ */ v(C, {
734
+ className: "max-w-lg",
735
+ children: [
736
+ /* @__PURE__ */ v(E, { children: [/* @__PURE__ */ _(D, { children: "Link OKR objectives" }), /* @__PURE__ */ _(w, { children: "Re-linking an objective that is already linked is a no-op, not an error." })] }),
737
+ /* @__PURE__ */ v("div", {
738
+ className: "max-h-80 overflow-y-auto space-y-1",
739
+ "data-testid": "product-add-objective-candidates",
740
+ children: [
741
+ y && /* @__PURE__ */ _("p", {
742
+ className: "text-sm text-muted-foreground py-4 text-center",
743
+ children: "Loading…"
744
+ }),
745
+ !y && A.length === 0 && /* @__PURE__ */ _("p", {
746
+ className: "text-sm text-muted-foreground py-4 text-center",
747
+ children: "Every objective in this workspace is already linked to this product."
748
+ }),
749
+ A.map((e) => {
750
+ let t = `product-add-objective-${e.id}`;
751
+ return /* @__PURE__ */ v("label", {
752
+ htmlFor: t,
753
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
754
+ children: [/* @__PURE__ */ _(x, {
755
+ id: t,
756
+ checked: l.has(e.id),
757
+ onCheckedChange: (t) => N(e.id, t === !0)
758
+ }), /* @__PURE__ */ _("span", {
759
+ className: "min-w-0 flex-1 truncate",
760
+ children: e.title
761
+ })]
762
+ }, e.id);
763
+ })
764
+ ]
765
+ }),
766
+ f && /* @__PURE__ */ _(a, { message: f }),
767
+ /* @__PURE__ */ _(T, { children: /* @__PURE__ */ _(b, {
768
+ onClick: () => void F(),
769
+ disabled: l.size === 0 || M.loading,
770
+ "data-testid": "product-add-objectives-confirm",
771
+ children: M.loading ? "Linking…" : `Link ${l.size} objective${l.size === 1 ? "" : "s"}`
772
+ }) })
773
+ ]
774
+ })]
775
+ });
776
+ }
777
+ //#endregion
778
+ export { q as ProductFeatureRequestsPanel, N as ProductHeaderActions, Q as ProductObjectivesPanel, H as ProductReleasesPanel, L as ProductRoadmapsPanel };
779
+
780
+ //# sourceMappingURL=ProductControls.js.map