@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
|
@@ -1,286 +1,206 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { PersonName as t } from "../people/People.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { useEntityId as n } from "../hooks/useEntityId.js";
|
|
4
|
+
import { enumLabel as r } from "../controls/WorkItemControls.js";
|
|
5
|
+
import { detailPath as i } from "../utils/rowNavigation.js";
|
|
6
|
+
import { PlanMagnetNotFoundPage as a } from "./NotFoundPage.js";
|
|
7
|
+
import { ProductFeatureRequestsPanel as o, ProductHeaderActions as s, ProductObjectivesPanel as c, ProductReleasesPanel as l, ProductRoadmapsPanel as u } from "../product/ProductControls.js";
|
|
8
|
+
import { Calendar as d, Loader2 as f, Package as p, Pencil as m, Target as h, Users as g } from "lucide-react";
|
|
9
|
+
import { gql as _ } from "@apollo/client";
|
|
10
|
+
import { useQuery as v } from "@apollo/client/react";
|
|
11
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
12
|
+
import { Badge as x, Button as S, Card as C, CardContent as w, CardHeader as T, CardTitle as E } from "@burdenoff/fe-libs/ui";
|
|
11
13
|
//#region src/planmagnet/pages/ProductsProductDetailPagePage.tsx
|
|
12
|
-
var
|
|
13
|
-
query
|
|
14
|
-
|
|
14
|
+
var D = _`
|
|
15
|
+
query GetProductDetail($id: ID!) {
|
|
16
|
+
planmagnetProduct(id: $id) {
|
|
15
17
|
id
|
|
16
18
|
workspaceId
|
|
17
19
|
name
|
|
18
20
|
description
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
isPublic
|
|
21
|
+
vision
|
|
22
|
+
lifecycleStage
|
|
22
23
|
ownerId
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
archivedAt
|
|
25
|
+
northStar {
|
|
26
|
+
name
|
|
27
|
+
unit
|
|
28
|
+
target
|
|
29
|
+
current
|
|
30
|
+
window
|
|
31
|
+
measuredAt
|
|
32
|
+
progressRatio
|
|
33
|
+
}
|
|
34
|
+
roadmaps {
|
|
26
35
|
id
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
name
|
|
37
|
+
}
|
|
38
|
+
releases {
|
|
39
|
+
id
|
|
40
|
+
name
|
|
41
|
+
version
|
|
29
42
|
status
|
|
30
|
-
startDate
|
|
31
|
-
endDate
|
|
32
|
-
color
|
|
33
|
-
swimlane
|
|
34
|
-
sortOrder
|
|
35
43
|
}
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
`, x = d`
|
|
39
|
-
query ListFeatureRequestsForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {
|
|
40
|
-
planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: $pagination) {
|
|
41
|
-
items {
|
|
44
|
+
featureRequests {
|
|
42
45
|
id
|
|
43
46
|
title
|
|
44
|
-
description
|
|
45
47
|
status
|
|
46
|
-
priorityScore
|
|
47
|
-
voteCount
|
|
48
|
-
businessValue
|
|
49
|
-
createdAt
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
`, S = d`
|
|
55
|
-
query ListReleasesForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {
|
|
56
|
-
planmagnetReleases(workspaceId: $workspaceId, pagination: $pagination) {
|
|
57
|
-
items {
|
|
49
|
+
objectives {
|
|
58
50
|
id
|
|
59
|
-
|
|
60
|
-
version
|
|
51
|
+
title
|
|
61
52
|
status
|
|
62
|
-
plannedDate
|
|
63
53
|
}
|
|
64
|
-
|
|
54
|
+
createdAt
|
|
55
|
+
updatedAt
|
|
65
56
|
}
|
|
66
57
|
}
|
|
67
58
|
`;
|
|
68
|
-
function
|
|
69
|
-
let
|
|
70
|
-
variables: { id:
|
|
71
|
-
skip: !
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
skip: !d
|
|
78
|
-
}), { data: O } = f(S, {
|
|
79
|
-
variables: {
|
|
80
|
-
workspaceId: d,
|
|
81
|
-
pagination: { first: 20 }
|
|
82
|
-
},
|
|
83
|
-
skip: !d
|
|
59
|
+
function O() {
|
|
60
|
+
let _ = e(), O = n("productId") ?? "", { data: k, loading: A, error: j } = v(D, {
|
|
61
|
+
variables: { id: O },
|
|
62
|
+
skip: !O
|
|
63
|
+
});
|
|
64
|
+
if (!O) return /* @__PURE__ */ y("div", {
|
|
65
|
+
className: "p-6 text-muted-foreground",
|
|
66
|
+
children: "No product selected. Open one from the list."
|
|
84
67
|
});
|
|
85
|
-
if (
|
|
68
|
+
if (A && !k) return /* @__PURE__ */ y("div", {
|
|
86
69
|
className: "flex items-center justify-center p-12",
|
|
87
|
-
children: /* @__PURE__ */
|
|
70
|
+
children: /* @__PURE__ */ y(f, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
88
71
|
});
|
|
89
|
-
if (
|
|
72
|
+
if (j) return /* @__PURE__ */ b("div", {
|
|
90
73
|
className: "p-6 text-destructive",
|
|
91
|
-
children: ["Failed to load product: ",
|
|
74
|
+
children: ["Failed to load product: ", j.message]
|
|
92
75
|
});
|
|
93
|
-
let
|
|
94
|
-
|
|
95
|
-
className: "p-6 text-muted-foreground",
|
|
96
|
-
children: "Product not found."
|
|
97
|
-
});
|
|
98
|
-
let A = k.items ?? [], j = D?.planmagnetFeatureRequests?.items ?? [], M = O?.planmagnetReleases?.items ?? [], N = D?.planmagnetFeatureRequests?.totalCount ?? 0, P = O?.planmagnetReleases?.totalCount ?? 0;
|
|
99
|
-
return /* @__PURE__ */ m("div", {
|
|
76
|
+
let M = k?.planmagnetProduct;
|
|
77
|
+
return M ? /* @__PURE__ */ b("div", {
|
|
100
78
|
className: "space-y-6 p-6",
|
|
101
79
|
children: [
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
className: "flex items-
|
|
104
|
-
children: [/* @__PURE__ */
|
|
105
|
-
className: "flex items-center gap-
|
|
106
|
-
children:
|
|
107
|
-
className: "
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
80
|
+
/* @__PURE__ */ b("div", {
|
|
81
|
+
className: "flex items-start justify-between gap-4",
|
|
82
|
+
children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ b("div", {
|
|
83
|
+
className: "flex items-center gap-2",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ y(p, { className: "h-5 w-5 text-primary" }),
|
|
86
|
+
/* @__PURE__ */ y("h1", {
|
|
87
|
+
className: "text-2xl font-semibold",
|
|
88
|
+
children: M.name
|
|
89
|
+
}),
|
|
90
|
+
M.archivedAt && /* @__PURE__ */ y(x, {
|
|
91
|
+
variant: "outline",
|
|
92
|
+
children: "Archived"
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ y(x, {
|
|
95
|
+
variant: "secondary",
|
|
96
|
+
className: "capitalize",
|
|
97
|
+
children: r(M.lifecycleStage)
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
}), /* @__PURE__ */ y("p", {
|
|
101
|
+
className: "text-sm text-muted-foreground mt-1",
|
|
102
|
+
children: M.vision || "No vision set"
|
|
103
|
+
})] }), /* @__PURE__ */ y("div", {
|
|
114
104
|
className: "flex flex-col items-end gap-2",
|
|
115
|
-
children:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
children: /* @__PURE__ */ b("div", {
|
|
106
|
+
className: "flex gap-2",
|
|
107
|
+
children: [/* @__PURE__ */ b(S, {
|
|
108
|
+
size: "sm",
|
|
109
|
+
variant: "outline",
|
|
110
|
+
onClick: () => _(i("/products/edit-product-page", M.id)),
|
|
111
|
+
children: [/* @__PURE__ */ y(m, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
112
|
+
}), /* @__PURE__ */ y(s, {
|
|
113
|
+
productId: M.id,
|
|
114
|
+
name: M.name,
|
|
115
|
+
archivedAt: M.archivedAt
|
|
116
|
+
})]
|
|
117
|
+
})
|
|
119
118
|
})]
|
|
120
119
|
}),
|
|
121
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ b("div", {
|
|
122
121
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
123
122
|
children: [
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
value: P,
|
|
132
|
-
icon: a
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
label: "Feature requests in workspace",
|
|
136
|
-
value: N,
|
|
137
|
-
icon: s
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
label: "Owner",
|
|
141
|
-
value: /* @__PURE__ */ p(t, { id: k.ownerId }),
|
|
142
|
-
icon: l
|
|
143
|
-
}
|
|
144
|
-
].map((e) => /* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
145
|
-
className: "pt-4",
|
|
146
|
-
children: [/* @__PURE__ */ m("div", {
|
|
147
|
-
className: "flex items-center gap-2 text-muted-foreground text-sm",
|
|
148
|
-
children: [/* @__PURE__ */ p(e.icon, { className: "h-4 w-4" }), e.label]
|
|
149
|
-
}), /* @__PURE__ */ p("p", {
|
|
150
|
-
className: "text-xl font-semibold mt-1",
|
|
151
|
-
children: e.value
|
|
152
|
-
})]
|
|
153
|
-
}) }, e.label))
|
|
154
|
-
}),
|
|
155
|
-
/* @__PURE__ */ m("div", {
|
|
156
|
-
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
157
|
-
children: [/* @__PURE__ */ m("div", {
|
|
158
|
-
className: "lg:col-span-2 space-y-6",
|
|
159
|
-
children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: ["Latest feature requests in the workspace", N > j.length ? ` (${j.length} of ${N})` : ""] }) }), /* @__PURE__ */ m(_, {
|
|
160
|
-
className: "space-y-3",
|
|
161
|
-
children: [j.length === 0 && /* @__PURE__ */ p("p", {
|
|
162
|
-
className: "text-sm text-muted-foreground",
|
|
163
|
-
children: "No feature requests yet."
|
|
164
|
-
}), j.map((e) => /* @__PURE__ */ m("div", {
|
|
165
|
-
className: "flex items-center justify-between rounded-md border p-3",
|
|
166
|
-
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
167
|
-
className: "text-sm font-medium",
|
|
168
|
-
children: e.title
|
|
169
|
-
}), /* @__PURE__ */ m("p", {
|
|
170
|
-
className: "text-xs text-muted-foreground",
|
|
171
|
-
children: [
|
|
172
|
-
"Votes: ",
|
|
173
|
-
e.voteCount,
|
|
174
|
-
" · Business Value: ",
|
|
175
|
-
e.businessValue ?? "N/A"
|
|
176
|
-
]
|
|
177
|
-
})] }), /* @__PURE__ */ p("div", {
|
|
178
|
-
className: "flex items-center gap-2",
|
|
179
|
-
children: /* @__PURE__ */ p(h, {
|
|
180
|
-
variant: "secondary",
|
|
181
|
-
className: "capitalize text-xs",
|
|
182
|
-
children: e.status.toLowerCase().replace("_", " ")
|
|
183
|
-
})
|
|
184
|
-
})]
|
|
185
|
-
}, e.id))]
|
|
186
|
-
})] }), /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
|
|
187
|
-
"Roadmap Items (",
|
|
188
|
-
A.length,
|
|
189
|
-
")"
|
|
190
|
-
] }) }), /* @__PURE__ */ m(_, {
|
|
191
|
-
className: "space-y-3",
|
|
192
|
-
children: [A.length === 0 && /* @__PURE__ */ p("p", {
|
|
193
|
-
className: "text-sm text-muted-foreground",
|
|
194
|
-
children: "No roadmap items linked."
|
|
195
|
-
}), A.map((e) => /* @__PURE__ */ m("div", {
|
|
196
|
-
className: "flex items-center justify-between rounded-md border p-3",
|
|
197
|
-
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
198
|
-
className: "text-sm font-medium",
|
|
199
|
-
children: e.title
|
|
200
|
-
}), /* @__PURE__ */ m("p", {
|
|
201
|
-
className: "text-xs text-muted-foreground",
|
|
202
|
-
children: [
|
|
203
|
-
e.startDate ? new Date(e.startDate).toLocaleDateString() : "No start date",
|
|
204
|
-
" - ",
|
|
205
|
-
e.endDate ? new Date(e.endDate).toLocaleDateString() : "No end date"
|
|
206
|
-
]
|
|
207
|
-
})] }), /* @__PURE__ */ p(h, {
|
|
208
|
-
variant: "outline",
|
|
209
|
-
className: "capitalize text-xs",
|
|
210
|
-
children: e.status.toLowerCase().replace("_", " ")
|
|
211
|
-
})]
|
|
212
|
-
}, e.id))]
|
|
213
|
-
})] })]
|
|
214
|
-
}), /* @__PURE__ */ m("div", {
|
|
215
|
-
className: "space-y-6",
|
|
216
|
-
children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Latest releases in the workspace" }) }), /* @__PURE__ */ m(_, {
|
|
217
|
-
className: "space-y-3",
|
|
218
|
-
children: [M.length === 0 && /* @__PURE__ */ p("p", {
|
|
219
|
-
className: "text-sm text-muted-foreground",
|
|
220
|
-
children: "No releases yet."
|
|
221
|
-
}), M.map((e) => /* @__PURE__ */ m("div", {
|
|
222
|
-
className: "border-b pb-2 last:border-0 last:pb-0",
|
|
123
|
+
/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
|
|
124
|
+
className: "pt-4",
|
|
125
|
+
children: [/* @__PURE__ */ b("div", {
|
|
126
|
+
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
127
|
+
children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), "North Star"]
|
|
128
|
+
}), M.northStar ? /* @__PURE__ */ b("p", {
|
|
129
|
+
className: "text-sm font-bold mt-1 truncate",
|
|
223
130
|
children: [
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
variant: "outline",
|
|
231
|
-
className: "text-xs capitalize",
|
|
232
|
-
children: e.status.toLowerCase().replace("_", " ")
|
|
233
|
-
})]
|
|
234
|
-
}),
|
|
235
|
-
/* @__PURE__ */ p("p", {
|
|
236
|
-
className: "text-xs text-muted-foreground",
|
|
237
|
-
children: e.name
|
|
238
|
-
}),
|
|
239
|
-
/* @__PURE__ */ m("div", {
|
|
240
|
-
className: "flex items-center gap-1 mt-1 text-xs text-muted-foreground",
|
|
241
|
-
children: [/* @__PURE__ */ p(i, { className: "h-3 w-3" }), e.plannedDate ? new Date(e.plannedDate).toLocaleDateString() : "No date set"]
|
|
242
|
-
})
|
|
131
|
+
M.northStar.current,
|
|
132
|
+
" / ",
|
|
133
|
+
M.northStar.target,
|
|
134
|
+
" ",
|
|
135
|
+
M.northStar.unit,
|
|
136
|
+
M.northStar.progressRatio == null ? "" : ` (${Math.round(M.northStar.progressRatio * 100)}%)`
|
|
243
137
|
]
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
138
|
+
}) : /* @__PURE__ */ y("p", {
|
|
139
|
+
className: "text-sm font-medium mt-1 text-muted-foreground",
|
|
140
|
+
children: "Not set"
|
|
141
|
+
})]
|
|
142
|
+
}) }),
|
|
143
|
+
/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
|
|
144
|
+
className: "pt-4",
|
|
145
|
+
children: [/* @__PURE__ */ b("div", {
|
|
146
|
+
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
147
|
+
children: [/* @__PURE__ */ y(g, { className: "h-4 w-4" }), "Owner"]
|
|
148
|
+
}), /* @__PURE__ */ y("p", {
|
|
149
|
+
className: "text-sm font-bold mt-1 truncate",
|
|
150
|
+
children: /* @__PURE__ */ y(t, { id: M.ownerId })
|
|
151
|
+
})]
|
|
152
|
+
}) }),
|
|
153
|
+
/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
|
|
154
|
+
className: "pt-4",
|
|
155
|
+
children: [/* @__PURE__ */ b("div", {
|
|
156
|
+
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
157
|
+
children: [/* @__PURE__ */ y(d, { className: "h-4 w-4" }), "Created"]
|
|
158
|
+
}), /* @__PURE__ */ y("p", {
|
|
159
|
+
className: "text-xl font-bold mt-1",
|
|
160
|
+
children: new Date(M.createdAt).toLocaleDateString()
|
|
161
|
+
})]
|
|
162
|
+
}) }),
|
|
163
|
+
/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
|
|
164
|
+
className: "pt-4",
|
|
165
|
+
children: [/* @__PURE__ */ b("div", {
|
|
166
|
+
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
167
|
+
children: [/* @__PURE__ */ y(d, { className: "h-4 w-4" }), "Updated"]
|
|
168
|
+
}), /* @__PURE__ */ y("p", {
|
|
169
|
+
className: "text-xl font-bold mt-1",
|
|
170
|
+
children: new Date(M.updatedAt).toLocaleDateString()
|
|
171
|
+
})]
|
|
172
|
+
}) })
|
|
173
|
+
]
|
|
174
|
+
}),
|
|
175
|
+
M.description && /* @__PURE__ */ y(C, { children: /* @__PURE__ */ y(w, {
|
|
176
|
+
className: "pt-4 text-sm text-muted-foreground",
|
|
177
|
+
children: M.description
|
|
178
|
+
}) }),
|
|
179
|
+
/* @__PURE__ */ b("div", {
|
|
180
|
+
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ b(C, { children: [/* @__PURE__ */ y(T, { children: /* @__PURE__ */ y(E, { children: "Roadmaps" }) }), /* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(u, {
|
|
183
|
+
productId: M.id,
|
|
184
|
+
roadmaps: M.roadmaps
|
|
185
|
+
}) })] }),
|
|
186
|
+
/* @__PURE__ */ b(C, { children: [/* @__PURE__ */ y(T, { children: /* @__PURE__ */ y(E, { children: "Releases" }) }), /* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(l, {
|
|
187
|
+
productId: M.id,
|
|
188
|
+
releases: M.releases
|
|
189
|
+
}) })] }),
|
|
190
|
+
/* @__PURE__ */ b(C, { children: [/* @__PURE__ */ y(T, { children: /* @__PURE__ */ y(E, { children: "Feature Requests" }) }), /* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(o, {
|
|
191
|
+
productId: M.id,
|
|
192
|
+
featureRequests: M.featureRequests
|
|
193
|
+
}) })] }),
|
|
194
|
+
/* @__PURE__ */ b(C, { children: [/* @__PURE__ */ y(T, { children: /* @__PURE__ */ y(E, { children: "OKR Objectives" }) }), /* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(c, {
|
|
195
|
+
productId: M.id,
|
|
196
|
+
objectives: M.objectives
|
|
197
|
+
}) })] })
|
|
198
|
+
]
|
|
279
199
|
})
|
|
280
200
|
]
|
|
281
|
-
});
|
|
201
|
+
}) : /* @__PURE__ */ y(a, {});
|
|
282
202
|
}
|
|
283
203
|
//#endregion
|
|
284
|
-
export {
|
|
204
|
+
export { O as ProductsProductDetailPagePage };
|
|
285
205
|
|
|
286
206
|
//# sourceMappingURL=ProductsProductDetailPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsProductDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Package, GitBranch, Map, Star, Users, Calendar, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\nimport { PersonName } from '../people/People';\nimport { PlanningFocusBadge } from '../roadmaps/planningFocus';\nimport { RoadmapHeaderActions } from '../roadmaps/RoadmapControls';\n\nconst GET_ROADMAP = gql`\n query GetRoadmapDetail($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n workspaceId\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n title\n description\n status\n startDate\n endDate\n color\n swimlane\n sortOrder\n }\n }\n }\n`;\n\nconst LIST_FEATURE_REQUESTS_FOR_PRODUCT = gql`\n query ListFeatureRequestsForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n title\n description\n status\n priorityScore\n voteCount\n businessValue\n createdAt\n }\n totalCount\n }\n }\n`;\n\nconst LIST_RELEASES_FOR_PRODUCT = gql`\n query ListReleasesForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetReleases(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n version\n status\n plannedDate\n }\n totalCount\n }\n }\n`;\n\ninterface RoadmapItemNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n startDate: string | null;\n endDate: string | null;\n}\n\ninterface FeatureRequestNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n priorityScore: number | null;\n voteCount: number;\n businessValue: number | null;\n}\n\ninterface ReleaseNode {\n id: string;\n name: string;\n version: string;\n status: string;\n plannedDate: string | null;\n}\n\nexport function ProductsProductDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n const {\n data: featuresData,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(LIST_FEATURE_REQUESTS_FOR_PRODUCT, {\n variables: { workspaceId, pagination: { first: 20 } },\n skip: !workspaceId,\n });\n\n const {\n data: releasesData,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(LIST_RELEASES_FOR_PRODUCT, {\n variables: { workspaceId, pagination: { first: 20 } },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load product: {error.message}</div>;\n }\n\n const roadmap = data?.planmagnetRoadmap;\n if (!roadmap) return <div className=\"p-6 text-muted-foreground\">Product not found.</div>;\n\n const roadmapItems: RoadmapItemNode[] = roadmap.items ?? [];\n const features: FeatureRequestNode[] = featuresData?.planmagnetFeatureRequests?.items ?? [];\n const releases: ReleaseNode[] = releasesData?.planmagnetReleases?.items ?? [];\n const totalFeatures = featuresData?.planmagnetFeatureRequests?.totalCount ?? 0;\n const totalReleases = releasesData?.planmagnetReleases?.totalCount ?? 0;\n\n const statusColor: Record<string, string> = {\n SUBMITTED: 'bg-gray-100 text-gray-800',\n UNDER_REVIEW: 'bg-yellow-100 text-yellow-800',\n PLANNED: 'bg-blue-100 text-blue-800',\n IN_PROGRESS: 'bg-purple-100 text-purple-800',\n COMPLETED: 'bg-green-100 text-green-800',\n DECLINED: 'bg-red-100 text-red-800',\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{roadmap.name}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {roadmap.description ?? 'No description'}\n </p>\n </div>\n </div>\n {/* Edit and Delete (BOFF-7941): the Edit page existed and nothing linked to it. */}\n <div className=\"flex flex-col items-end gap-2\">\n <RoadmapHeaderActions roadmapId={roadmap.id} name={roadmap.name} />\n <PlanningFocusBadge focus={roadmap.planningFocus} />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {[\n { label: 'Roadmap Items', value: roadmapItems.length, icon: Star },\n // A product (a roadmap) has no project scope of its own, so these are the WORKSPACE's numbers — said so,\n // rather than shown as if they belonged to the product (BOFF-7991).\n { label: 'Releases in workspace', value: totalReleases, icon: GitBranch },\n { label: 'Feature requests in workspace', value: totalFeatures, icon: Map },\n // The name, as the Details card below shows it — not the owner's UUID in large type (BOFF-7976).\n { label: 'Owner', value: <PersonName id={roadmap.ownerId} />, icon: Users },\n ].map((s) => (\n <Card key={s.label}>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-sm\">\n <s.icon className=\"h-4 w-4\" />\n {s.label}\n </div>\n <p className=\"text-xl font-semibold mt-1\">{s.value}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>\n Latest feature requests in the workspace\n {totalFeatures > features.length ? ` (${features.length} of ${totalFeatures})` : ''}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {features.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No feature requests yet.</p>\n )}\n {features.map((f) => (\n <div key={f.id} className=\"flex items-center justify-between rounded-md border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{f.title}</p>\n <p className=\"text-xs text-muted-foreground\">\n Votes: {f.voteCount} · Business Value: {f.businessValue ?? 'N/A'}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"capitalize text-xs\">\n {f.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Roadmap Items ({roadmapItems.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {roadmapItems.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No roadmap items linked.</p>\n )}\n {roadmapItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between rounded-md border p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground\">\n {item.startDate\n ? new Date(item.startDate).toLocaleDateString()\n : 'No start date'}\n {' - '}\n {item.endDate ? new Date(item.endDate).toLocaleDateString() : 'No end date'}\n </p>\n </div>\n <Badge variant=\"outline\" className=\"capitalize text-xs\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Latest releases in the workspace</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {releases.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No releases yet.</p>\n )}\n {releases.map((r) => (\n <div key={r.id} className=\"border-b pb-2 last:border-0 last:pb-0\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium\">{r.version}</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {r.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n <p className=\"text-xs text-muted-foreground\">{r.name}</p>\n <div className=\"flex items-center gap-1 mt-1 text-xs text-muted-foreground\">\n <Calendar className=\"h-3 w-3\" />\n {r.plannedDate ? new Date(r.plannedDate).toLocaleDateString() : 'No date set'}\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(roadmap.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(roadmap.updatedAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>\n <PersonName id={roadmap.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Visibility</span>\n <span>{roadmap.isPublic ? 'Public' : 'Private'}</span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsProductDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport { Package, Target, Users, Calendar, Loader2, Pencil } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { PlanMagnetNotFoundPage } from './NotFoundPage';\nimport {\n ProductHeaderActions,\n ProductRoadmapsPanel,\n ProductReleasesPanel,\n ProductFeatureRequestsPanel,\n ProductObjectivesPanel,\n type ProductRoadmapRef,\n type ProductReleaseRef,\n type ProductFeatureRequestRef,\n type ProductObjectiveRef,\n} from '../product/ProductControls';\n\n/**\n * Product detail (BOFF-8045): the product's own fields, its North Star, and a Links\n * management panel for roadmaps/releases/feature requests/OKR objectives. Every relation\n * is selected directly off `planmagnetProduct(id)` rather than a separate workspace-wide\n * query — before this rewrite the page queried EVERY feature request/release in the\n * workspace regardless of product, which is the pre-existing bug this fixes (there was no\n * productId to filter on until now).\n */\n\nconst GET_PRODUCT_DETAIL = gql`\n query GetProductDetail($id: ID!) {\n planmagnetProduct(id: $id) {\n id\n workspaceId\n name\n description\n vision\n lifecycleStage\n ownerId\n archivedAt\n northStar {\n name\n unit\n target\n current\n window\n measuredAt\n progressRatio\n }\n roadmaps {\n id\n name\n }\n releases {\n id\n name\n version\n status\n }\n featureRequests {\n id\n title\n status\n }\n objectives {\n id\n title\n status\n }\n createdAt\n updatedAt\n }\n }\n`;\n\ninterface ProductDetail {\n id: string;\n workspaceId: string;\n name: string;\n description: string | null;\n vision: string;\n lifecycleStage: string;\n ownerId: string;\n archivedAt: string | null;\n northStar: {\n name: string;\n unit: string;\n target: number;\n current: number;\n window: string;\n measuredAt: string | null;\n progressRatio: number | null;\n } | null;\n roadmaps: ProductRoadmapRef[];\n releases: ProductReleaseRef[];\n featureRequests: ProductFeatureRequestRef[];\n objectives: ProductObjectiveRef[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport function ProductsProductDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const productId = useEntityId('productId') ?? '';\n\n const { data, loading, error } = useQuery<{ planmagnetProduct: ProductDetail | null }>(\n GET_PRODUCT_DETAIL,\n { variables: { id: productId }, skip: !productId }\n );\n\n if (!productId) {\n return (\n <div className=\"p-6 text-muted-foreground\">No product selected. Open one from the list.</div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page (it\n // unmounts every component on it and loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load product: {error.message}</div>;\n }\n\n const product = data?.planmagnetProduct;\n // Deleted, unauthorized or an unknown id all read back the same way (no cross-tenant\n // existence leak) — the real 404 page, not an inline message.\n if (!product) return <PlanMagnetNotFoundPage />;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Package className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{product.name}</h1>\n {product.archivedAt && <Badge variant=\"outline\">Archived</Badge>}\n <Badge variant=\"secondary\" className=\"capitalize\">\n {enumLabel(product.lifecycleStage)}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">{product.vision || 'No vision set'}</p>\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => navigate(detailPath('/products/edit-product-page', product.id))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ProductHeaderActions\n productId={product.id}\n name={product.name}\n archivedAt={product.archivedAt}\n />\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n North Star\n </div>\n {product.northStar ? (\n <p className=\"text-sm font-bold mt-1 truncate\">\n {product.northStar.current} / {product.northStar.target} {product.northStar.unit}\n {product.northStar.progressRatio != null\n ? ` (${Math.round(product.northStar.progressRatio * 100)}%)`\n : ''}\n </p>\n ) : (\n <p className=\"text-sm font-medium mt-1 text-muted-foreground\">Not set</p>\n )}\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Users className=\"h-4 w-4\" />\n Owner\n </div>\n <p className=\"text-sm font-bold mt-1 truncate\">\n <PersonName id={product.ownerId} />\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Calendar className=\"h-4 w-4\" />\n Created\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {new Date(product.createdAt).toLocaleDateString()}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Calendar className=\"h-4 w-4\" />\n Updated\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {new Date(product.updatedAt).toLocaleDateString()}\n </p>\n </CardContent>\n </Card>\n </div>\n\n {product.description && (\n <Card>\n <CardContent className=\"pt-4 text-sm text-muted-foreground\">\n {product.description}\n </CardContent>\n </Card>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Roadmaps</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductRoadmapsPanel productId={product.id} roadmaps={product.roadmaps} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Releases</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductReleasesPanel productId={product.id} releases={product.releases} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Feature Requests</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductFeatureRequestsPanel\n productId={product.id}\n featureRequests={product.featureRequests}\n />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>OKR Objectives</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductObjectivesPanel productId={product.id} objectives={product.objectives} />\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsProductsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductsProductsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductsPagePage.tsx"],"names":[],"mappings":"AA+EA,wBAAgB,wBAAwB,4CAgMvC"}
|