@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.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +217 -209
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.js +198 -99
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +260 -170
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js +283 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +166 -246
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +147 -117
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/product/ProductControls.d.ts +52 -0
- package/dist/planmagnet/product/ProductControls.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductControls.js +780 -0
- package/dist/planmagnet/product/ProductControls.js.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
- 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
|