@burdenoff/microfe-planmagnet 2026.1002.1 → 2026.1002.2
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/generated/wspace-operations.d.ts +1814 -401
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +5167 -54
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +172 -160
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +95 -83
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +7 -2
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +10 -9
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/index.d.ts +1 -1
- package/dist/planmagnet/index.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +105 -82
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
- package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
- package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
- package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +156 -154
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
- package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
- package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
- package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
- package/dist/planmagnet/product/ProductControls.js +1 -1
- package/dist/planmagnet/product/ProductControls.js.map +1 -1
- package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
- package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
- package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
- package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
- package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
- package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
- package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
- package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductLineControls.js +417 -0
- package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
- package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
- package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
- package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
- package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
- package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
- package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
- package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
- package/dist/planmagnet/product/hierarchy.d.ts +127 -0
- package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
- package/dist/planmagnet/product/hierarchy.js +173 -0
- package/dist/planmagnet/product/hierarchy.js.map +1 -0
- package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
- package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
- package/dist/planmagnet/product/hierarchyOperations.js +440 -0
- package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
- package/dist/planmagnet/product/refusal.d.ts +14 -0
- package/dist/planmagnet/product/refusal.d.ts.map +1 -0
- package/dist/planmagnet/product/refusal.js +10 -0
- package/dist/planmagnet/product/refusal.js.map +1 -0
- package/dist/planmagnet/product/routes.d.ts +4 -0
- package/dist/planmagnet/product/routes.d.ts.map +1 -1
- package/dist/planmagnet/product/routes.js +2 -2
- package/dist/planmagnet/product/routes.js.map +1 -1
- package/dist/shared/utils/i18n.d.ts +3 -0
- package/dist/shared/utils/i18n.d.ts.map +1 -1
- package/dist/shared/utils/i18n.js +1 -1
- package/dist/shared/utils/i18n.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,13 +5,16 @@ import { detailPath as r } from "../utils/rowNavigation.js";
|
|
|
5
5
|
import { useEntityId as i } from "../hooks/useEntityId.js";
|
|
6
6
|
import { PlanMagnetNotFoundPage as a } from "./NotFoundPage.js";
|
|
7
7
|
import { ProductAboutPanel as o, ProductCompetitorsPanel as s, ProductFeatureRequestsPanel as c, ProductHeaderActions as l, ProductObjectivesPanel as u, ProductReleasesPanel as d, ProductRoadmapsPanel as f } from "../product/ProductControls.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { ProductFeaturesPanel as p } from "../product/ProductFeatureControls.js";
|
|
9
|
+
import { ProductLinePicker as m } from "../product/ProductLineControls.js";
|
|
10
|
+
import { ProductTeamsPanel as h } from "../product/ProductTeamsPanel.js";
|
|
11
|
+
import { Calendar as g, ListTree as _, Loader2 as v, Package as y, Pencil as b, Target as x, Users as S } from "lucide-react";
|
|
12
|
+
import { gql as C } from "@apollo/client";
|
|
13
|
+
import { useQuery as w } from "@apollo/client/react";
|
|
14
|
+
import { jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
15
|
+
import { Badge as D, Button as O, Card as k, CardContent as A, CardHeader as j, CardTitle as M } from "@burdenoff/fe-libs/ui";
|
|
13
16
|
//#region src/planmagnet/pages/ProductsProductDetailPagePage.tsx
|
|
14
|
-
var
|
|
17
|
+
var N = C`
|
|
15
18
|
query GetProductDetail($id: ID!) {
|
|
16
19
|
planmagnetProduct(id: $id) {
|
|
17
20
|
id
|
|
@@ -66,158 +69,178 @@ var k = y`
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
`;
|
|
69
|
-
function
|
|
70
|
-
let
|
|
71
|
-
variables: { id:
|
|
72
|
-
skip: !
|
|
72
|
+
function P() {
|
|
73
|
+
let C = e(), P = i("productId") ?? "", { data: F, loading: I, error: L } = w(N, {
|
|
74
|
+
variables: { id: P },
|
|
75
|
+
skip: !P
|
|
73
76
|
});
|
|
74
|
-
if (!
|
|
77
|
+
if (!P) return /* @__PURE__ */ T("div", {
|
|
75
78
|
className: "p-6 text-muted-foreground",
|
|
76
79
|
children: "No product selected. Open one from the list."
|
|
77
80
|
});
|
|
78
|
-
if (
|
|
81
|
+
if (I && !F) return /* @__PURE__ */ T("div", {
|
|
79
82
|
className: "flex items-center justify-center p-12",
|
|
80
|
-
children: /* @__PURE__ */
|
|
83
|
+
children: /* @__PURE__ */ T(v, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
81
84
|
});
|
|
82
|
-
if (
|
|
85
|
+
if (L) return /* @__PURE__ */ E("div", {
|
|
83
86
|
className: "p-6 text-destructive",
|
|
84
|
-
children: ["Failed to load product: ",
|
|
87
|
+
children: ["Failed to load product: ", L.message]
|
|
85
88
|
});
|
|
86
|
-
let
|
|
87
|
-
return
|
|
89
|
+
let R = F?.planmagnetProduct;
|
|
90
|
+
return R ? /* @__PURE__ */ E("div", {
|
|
88
91
|
className: "space-y-6 p-6",
|
|
89
92
|
children: [
|
|
90
|
-
/* @__PURE__ */
|
|
93
|
+
/* @__PURE__ */ E("div", {
|
|
91
94
|
className: "flex items-start justify-between gap-4",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ E("div", {
|
|
93
96
|
className: "flex items-center gap-2",
|
|
94
97
|
children: [
|
|
95
|
-
/* @__PURE__ */
|
|
96
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ T(y, { className: "h-5 w-5 text-primary" }),
|
|
99
|
+
/* @__PURE__ */ T("h1", {
|
|
97
100
|
className: "text-2xl font-semibold",
|
|
98
|
-
children:
|
|
101
|
+
children: R.name
|
|
99
102
|
}),
|
|
100
|
-
|
|
103
|
+
R.archivedAt && /* @__PURE__ */ T(D, {
|
|
101
104
|
variant: "outline",
|
|
102
105
|
children: "Archived"
|
|
103
106
|
}),
|
|
104
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ T(D, {
|
|
105
108
|
variant: "secondary",
|
|
106
109
|
className: "capitalize",
|
|
107
|
-
children: n(
|
|
110
|
+
children: n(R.lifecycleStage)
|
|
108
111
|
})
|
|
109
112
|
]
|
|
110
|
-
}), /* @__PURE__ */
|
|
113
|
+
}), /* @__PURE__ */ T("p", {
|
|
111
114
|
className: "text-sm text-muted-foreground mt-1",
|
|
112
|
-
children:
|
|
113
|
-
})] }), /* @__PURE__ */
|
|
115
|
+
children: R.vision || "No vision set"
|
|
116
|
+
})] }), /* @__PURE__ */ T("div", {
|
|
114
117
|
className: "flex flex-col items-end gap-2",
|
|
115
|
-
children: /* @__PURE__ */
|
|
118
|
+
children: /* @__PURE__ */ E("div", {
|
|
116
119
|
className: "flex gap-2",
|
|
117
|
-
children: [/* @__PURE__ */
|
|
120
|
+
children: [/* @__PURE__ */ E(O, {
|
|
118
121
|
size: "sm",
|
|
119
122
|
variant: "outline",
|
|
120
|
-
onClick: () =>
|
|
121
|
-
children: [/* @__PURE__ */
|
|
122
|
-
}), /* @__PURE__ */
|
|
123
|
-
productId:
|
|
124
|
-
name:
|
|
125
|
-
archivedAt:
|
|
123
|
+
onClick: () => C(r("/products/edit-product-page", R.id)),
|
|
124
|
+
children: [/* @__PURE__ */ T(b, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
125
|
+
}), /* @__PURE__ */ T(l, {
|
|
126
|
+
productId: R.id,
|
|
127
|
+
name: R.name,
|
|
128
|
+
archivedAt: R.archivedAt
|
|
126
129
|
})]
|
|
127
130
|
})
|
|
128
131
|
})]
|
|
129
132
|
}),
|
|
130
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ E("div", {
|
|
131
134
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
132
135
|
children: [
|
|
133
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
|
|
134
137
|
className: "pt-4",
|
|
135
|
-
children: [/* @__PURE__ */
|
|
138
|
+
children: [/* @__PURE__ */ E("div", {
|
|
136
139
|
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
137
|
-
children: [/* @__PURE__ */ x
|
|
138
|
-
}),
|
|
140
|
+
children: [/* @__PURE__ */ T(x, { className: "h-4 w-4" }), "North Star"]
|
|
141
|
+
}), R.northStar ? /* @__PURE__ */ E("p", {
|
|
139
142
|
className: "text-sm font-bold mt-1 truncate",
|
|
140
143
|
children: [
|
|
141
|
-
|
|
144
|
+
R.northStar.current,
|
|
142
145
|
" / ",
|
|
143
|
-
|
|
146
|
+
R.northStar.target,
|
|
144
147
|
" ",
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
R.northStar.unit,
|
|
149
|
+
R.northStar.progressRatio == null ? "" : ` (${Math.round(R.northStar.progressRatio * 100)}%)`
|
|
147
150
|
]
|
|
148
|
-
}) : /* @__PURE__ */
|
|
151
|
+
}) : /* @__PURE__ */ T("p", {
|
|
149
152
|
className: "text-sm font-medium mt-1 text-muted-foreground",
|
|
150
153
|
children: "Not set"
|
|
151
154
|
})]
|
|
152
155
|
}) }),
|
|
153
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
|
|
154
157
|
className: "pt-4",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ E("div", {
|
|
156
159
|
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
157
|
-
children: [/* @__PURE__ */
|
|
158
|
-
}), /* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ T(S, { className: "h-4 w-4" }), "Owner"]
|
|
161
|
+
}), /* @__PURE__ */ T("p", {
|
|
159
162
|
className: "text-sm font-bold mt-1 truncate",
|
|
160
|
-
children: /* @__PURE__ */
|
|
163
|
+
children: /* @__PURE__ */ T(t, { id: R.ownerId })
|
|
161
164
|
})]
|
|
162
165
|
}) }),
|
|
163
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
|
|
164
167
|
className: "pt-4",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ E("div", {
|
|
166
169
|
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
168
|
-
}), /* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ T(g, { className: "h-4 w-4" }), "Created"]
|
|
171
|
+
}), /* @__PURE__ */ T("p", {
|
|
169
172
|
className: "text-xl font-bold mt-1",
|
|
170
|
-
children: new Date(
|
|
173
|
+
children: new Date(R.createdAt).toLocaleDateString()
|
|
171
174
|
})]
|
|
172
175
|
}) }),
|
|
173
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
|
|
174
177
|
className: "pt-4",
|
|
175
|
-
children: [/* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ E("div", {
|
|
176
179
|
className: "flex items-center gap-2 text-muted-foreground text-xs",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
178
|
-
}), /* @__PURE__ */
|
|
180
|
+
children: [/* @__PURE__ */ T(g, { className: "h-4 w-4" }), "Updated"]
|
|
181
|
+
}), /* @__PURE__ */ T("p", {
|
|
179
182
|
className: "text-xl font-bold mt-1",
|
|
180
|
-
children: new Date(
|
|
183
|
+
children: new Date(R.updatedAt).toLocaleDateString()
|
|
181
184
|
})]
|
|
182
185
|
}) })
|
|
183
186
|
]
|
|
184
187
|
}),
|
|
185
|
-
|
|
188
|
+
R.description && /* @__PURE__ */ T(k, { children: /* @__PURE__ */ T(A, {
|
|
186
189
|
className: "pt-4 text-sm text-muted-foreground",
|
|
187
|
-
children:
|
|
190
|
+
children: R.description
|
|
188
191
|
}) }),
|
|
189
|
-
/* @__PURE__ */
|
|
192
|
+
/* @__PURE__ */ E("div", {
|
|
193
|
+
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
194
|
+
children: [/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Product line" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(m, { productId: R.id }) })] }), /* @__PURE__ */ E(k, {
|
|
195
|
+
className: "lg:col-span-2",
|
|
196
|
+
children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Teams" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(h, { productId: R.id }) })]
|
|
197
|
+
})]
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ E(j, {
|
|
200
|
+
className: "flex flex-row items-center justify-between gap-2",
|
|
201
|
+
children: [/* @__PURE__ */ T(M, { children: "Features" }), /* @__PURE__ */ E(O, {
|
|
202
|
+
size: "sm",
|
|
203
|
+
variant: "ghost",
|
|
204
|
+
onClick: () => C("/products/product-hierarchy-page"),
|
|
205
|
+
"data-testid": "product-view-hierarchy",
|
|
206
|
+
children: [/* @__PURE__ */ T(_, { className: "mr-1 h-4 w-4" }), "View hierarchy"]
|
|
207
|
+
})]
|
|
208
|
+
}), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(p, {
|
|
209
|
+
productId: R.id,
|
|
210
|
+
productArchived: !!R.archivedAt
|
|
211
|
+
}) })] }),
|
|
212
|
+
/* @__PURE__ */ E("div", {
|
|
190
213
|
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
191
|
-
children: [/* @__PURE__ */
|
|
192
|
-
productId:
|
|
193
|
-
competitors:
|
|
214
|
+
children: [/* @__PURE__ */ T(o, { product: R }), /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Competitive landscape" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(s, {
|
|
215
|
+
productId: R.id,
|
|
216
|
+
competitors: R.competitors
|
|
194
217
|
}) })] })]
|
|
195
218
|
}),
|
|
196
|
-
/* @__PURE__ */
|
|
219
|
+
/* @__PURE__ */ E("div", {
|
|
197
220
|
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
198
221
|
children: [
|
|
199
|
-
/* @__PURE__ */
|
|
200
|
-
productId:
|
|
201
|
-
roadmaps:
|
|
222
|
+
/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Roadmaps" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(f, {
|
|
223
|
+
productId: R.id,
|
|
224
|
+
roadmaps: R.roadmaps
|
|
202
225
|
}) })] }),
|
|
203
|
-
/* @__PURE__ */
|
|
204
|
-
productId:
|
|
205
|
-
releases:
|
|
226
|
+
/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Releases" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(d, {
|
|
227
|
+
productId: R.id,
|
|
228
|
+
releases: R.releases
|
|
206
229
|
}) })] }),
|
|
207
|
-
/* @__PURE__ */
|
|
208
|
-
productId:
|
|
209
|
-
featureRequests:
|
|
230
|
+
/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Feature requests" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(c, {
|
|
231
|
+
productId: R.id,
|
|
232
|
+
featureRequests: R.featureRequests
|
|
210
233
|
}) })] }),
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
productId:
|
|
213
|
-
objectives:
|
|
234
|
+
/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "OKR Objectives" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(u, {
|
|
235
|
+
productId: R.id,
|
|
236
|
+
objectives: R.objectives
|
|
214
237
|
}) })] })
|
|
215
238
|
]
|
|
216
239
|
})
|
|
217
240
|
]
|
|
218
|
-
}) : /* @__PURE__ */
|
|
241
|
+
}) : /* @__PURE__ */ T(a, {});
|
|
219
242
|
}
|
|
220
243
|
//#endregion
|
|
221
|
-
export {
|
|
244
|
+
export { P as ProductsProductDetailPagePage };
|
|
222
245
|
|
|
223
246
|
//# sourceMappingURL=ProductsProductDetailPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
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 ProductAboutPanel,\n ProductCompetitorsPanel,\n type ProductRoadmapRef,\n type ProductReleaseRef,\n type ProductFeatureRequestRef,\n type ProductObjectiveRef,\n type ProductCompetitorRef,\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 mission\n valueProposition\n businessModel\n targetSegments\n competitors {\n id\n name\n description\n website\n }\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 mission: string | null;\n valueProposition: string | null;\n businessModel: string | null;\n targetSegments: string[];\n competitors: ProductCompetitorRef[];\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 {/* PRM-02: strategy narrative + competitive landscape. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <ProductAboutPanel product={product} />\n <Card>\n <CardHeader>\n <CardTitle>Competitive landscape</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductCompetitorsPanel productId={product.id} competitors={product.competitors} />\n </CardContent>\n </Card>\n </div>\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
|
+
{"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, ListTree } 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 ProductAboutPanel,\n ProductCompetitorsPanel,\n type ProductRoadmapRef,\n type ProductReleaseRef,\n type ProductFeatureRequestRef,\n type ProductObjectiveRef,\n type ProductCompetitorRef,\n} from '../product/ProductControls';\nimport { ProductLinePicker } from '../product/ProductLineControls';\nimport { ProductTeamsPanel } from '../product/ProductTeamsPanel';\nimport { ProductFeaturesPanel } from '../product/ProductFeatureControls';\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 * PRM-01 adds the product's place in the hierarchy: its product line, the teams that own it\n * (a link that grants no access), and its features — capabilities the team builds, distinct\n * from the feature requests below. Each of those sections runs its own query, so a field the\n * gateway does not serve yet fails that card, never this page's GetProductDetail.\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 mission\n valueProposition\n businessModel\n targetSegments\n competitors {\n id\n name\n description\n website\n }\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 mission: string | null;\n valueProposition: string | null;\n businessModel: string | null;\n targetSegments: string[];\n competitors: ProductCompetitorRef[];\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 {/* PRM-01: Product line > Product > Feature > Epic > Story. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Product line</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductLinePicker productId={product.id} />\n </CardContent>\n </Card>\n <Card className=\"lg:col-span-2\">\n <CardHeader>\n <CardTitle>Teams</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductTeamsPanel productId={product.id} />\n </CardContent>\n </Card>\n </div>\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2\">\n <CardTitle>Features</CardTitle>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => navigate('/products/product-hierarchy-page')}\n data-testid=\"product-view-hierarchy\"\n >\n <ListTree className=\"mr-1 h-4 w-4\" />\n View hierarchy\n </Button>\n </CardHeader>\n <CardContent>\n <ProductFeaturesPanel productId={product.id} productArchived={!!product.archivedAt} />\n </CardContent>\n </Card>\n\n {/* PRM-02: strategy narrative + competitive landscape. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <ProductAboutPanel product={product} />\n <Card>\n <CardHeader>\n <CardTitle>Competitive landscape</CardTitle>\n </CardHeader>\n <CardContent>\n <ProductCompetitorsPanel productId={product.id} competitors={product.competitors} />\n </CardContent>\n </Card>\n </div>\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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductsProductFeatureDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductFeatureDetailPagePage.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,oCAAoC,4CAwOnD"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { PersonName as t } from "../people/People.js";
|
|
3
|
+
import { useTr as n } from "../../shared/hooks/useTr.js";
|
|
4
|
+
import { ErrorLine as r } from "../controls/ConfirmButton.js";
|
|
5
|
+
import { detailPath as i } from "../utils/rowNavigation.js";
|
|
6
|
+
import { useEntityId as a } from "../hooks/useEntityId.js";
|
|
7
|
+
import { PlanMagnetNotFoundPage as o } from "./NotFoundPage.js";
|
|
8
|
+
import { PRODUCT_FEATURE_DETAIL as s } from "../product/hierarchyOperations.js";
|
|
9
|
+
import { parseServerDate as c } from "../product/hierarchy.js";
|
|
10
|
+
import { ProductFeatureHeaderActions as l, ProductFeatureStatusBadge as u } from "../product/ProductFeatureControls.js";
|
|
11
|
+
import { FeatureEpicsPanel as d, FeatureRequestsPanel as f } from "../product/FeatureLinkPanels.js";
|
|
12
|
+
import { ChevronRight as p, Loader2 as m, MessageSquare as h, Milestone as g, Puzzle as _, Users as v } from "lucide-react";
|
|
13
|
+
import { useQuery as y } from "@apollo/client/react";
|
|
14
|
+
import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
15
|
+
import { Badge as C, Card as w, CardContent as T, CardHeader as E, CardTitle as D } from "@burdenoff/fe-libs/ui";
|
|
16
|
+
//#region src/planmagnet/pages/ProductsProductFeatureDetailPagePage.tsx
|
|
17
|
+
function O() {
|
|
18
|
+
let O = n(), k = e(), A = a("productFeatureId") ?? "", { data: j, loading: M, error: N } = y(s, {
|
|
19
|
+
variables: { id: A },
|
|
20
|
+
skip: !A
|
|
21
|
+
});
|
|
22
|
+
if (!A) return /* @__PURE__ */ x("div", {
|
|
23
|
+
className: "p-6 text-muted-foreground",
|
|
24
|
+
children: O("planmagnet.productFeatures.detail.noneSelected", "No feature selected. Open one from its product’s page.")
|
|
25
|
+
});
|
|
26
|
+
if (M && !j) return /* @__PURE__ */ x("div", {
|
|
27
|
+
className: "flex items-center justify-center p-12",
|
|
28
|
+
children: /* @__PURE__ */ x(m, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
29
|
+
});
|
|
30
|
+
if (N && !j) return /* @__PURE__ */ x("div", {
|
|
31
|
+
className: "p-6 text-destructive",
|
|
32
|
+
children: O("planmagnet.productFeatures.detail.loadError", "Failed to load the feature: {{message}}", { message: N.message })
|
|
33
|
+
});
|
|
34
|
+
let P = j?.planmagnetProductFeature;
|
|
35
|
+
if (!P) return /* @__PURE__ */ x(o, {});
|
|
36
|
+
let F = !!P.archivedAt, I = c(P.updatedAt), L = c(P.createdAt);
|
|
37
|
+
return /* @__PURE__ */ S("div", {
|
|
38
|
+
className: "space-y-6 p-6",
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ S("nav", {
|
|
41
|
+
"aria-label": O("planmagnet.productFeatures.detail.breadcrumb", "Breadcrumb"),
|
|
42
|
+
className: "flex flex-wrap items-center gap-1 text-xs text-muted-foreground",
|
|
43
|
+
children: [
|
|
44
|
+
P.product?.productLine && /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("button", {
|
|
45
|
+
type: "button",
|
|
46
|
+
className: "hover:underline",
|
|
47
|
+
onClick: () => k("/products/product-lines-page"),
|
|
48
|
+
children: P.product.productLine.name
|
|
49
|
+
}), /* @__PURE__ */ x(p, { className: "h-3 w-3" })] }),
|
|
50
|
+
P.product ? /* @__PURE__ */ x("button", {
|
|
51
|
+
type: "button",
|
|
52
|
+
className: "hover:underline",
|
|
53
|
+
onClick: () => P.product && k(i("/products/product-detail-page", P.product.id)),
|
|
54
|
+
children: P.product.name
|
|
55
|
+
}) : /* @__PURE__ */ x("span", { children: O("planmagnet.productFeatures.detail.productGone", "Deleted product") }),
|
|
56
|
+
/* @__PURE__ */ x(p, { className: "h-3 w-3" }),
|
|
57
|
+
/* @__PURE__ */ x("span", {
|
|
58
|
+
className: "text-foreground",
|
|
59
|
+
children: P.name
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ S("div", {
|
|
64
|
+
className: "flex flex-wrap items-start justify-between gap-4",
|
|
65
|
+
children: [/* @__PURE__ */ S("div", {
|
|
66
|
+
className: "min-w-0",
|
|
67
|
+
children: [/* @__PURE__ */ S("div", {
|
|
68
|
+
className: "flex flex-wrap items-center gap-2",
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ x(_, { className: "h-5 w-5 text-primary" }),
|
|
71
|
+
/* @__PURE__ */ x("h1", {
|
|
72
|
+
className: "text-2xl font-semibold",
|
|
73
|
+
children: P.name
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ x(u, { status: P.status }),
|
|
76
|
+
F && /* @__PURE__ */ x(C, {
|
|
77
|
+
variant: "outline",
|
|
78
|
+
children: O("planmagnet.productFeatures.archived", "Archived")
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
}), /* @__PURE__ */ x("p", {
|
|
82
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
83
|
+
children: O("planmagnet.productFeatures.detail.subtitle", "A product feature — what the team is building. Feature requests are the feedback it answers.")
|
|
84
|
+
})]
|
|
85
|
+
}), /* @__PURE__ */ x(l, {
|
|
86
|
+
feature: P,
|
|
87
|
+
epicCount: P.epics.length,
|
|
88
|
+
requestCount: P.featureRequests.length
|
|
89
|
+
})]
|
|
90
|
+
}),
|
|
91
|
+
N && /* @__PURE__ */ x(r, { message: O("planmagnet.productFeatures.detail.refreshError", "The feature could not be refreshed: {{message}}", { message: N.message }) }),
|
|
92
|
+
F && /* @__PURE__ */ x("p", {
|
|
93
|
+
className: "rounded-md bg-muted p-3 text-sm text-muted-foreground",
|
|
94
|
+
children: O("planmagnet.productFeatures.detail.archivedNote", "This feature is archived. It keeps its epics and requests, but takes no new links until it is unarchived.")
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ S("div", {
|
|
97
|
+
className: "grid grid-cols-2 gap-4 md:grid-cols-4",
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ x(w, { children: /* @__PURE__ */ S(T, {
|
|
100
|
+
className: "pt-4",
|
|
101
|
+
children: [/* @__PURE__ */ x("p", {
|
|
102
|
+
className: "text-xs text-muted-foreground",
|
|
103
|
+
children: O("planmagnet.productFeatures.fields.status", "Status")
|
|
104
|
+
}), /* @__PURE__ */ x("div", {
|
|
105
|
+
className: "mt-1",
|
|
106
|
+
children: /* @__PURE__ */ x(u, { status: P.status })
|
|
107
|
+
})]
|
|
108
|
+
}) }),
|
|
109
|
+
/* @__PURE__ */ x(w, { children: /* @__PURE__ */ S(T, {
|
|
110
|
+
className: "pt-4",
|
|
111
|
+
children: [/* @__PURE__ */ S("div", {
|
|
112
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
113
|
+
children: [/* @__PURE__ */ x(v, { className: "h-4 w-4" }), O("planmagnet.productFeatures.fields.owner", "Owner")]
|
|
114
|
+
}), /* @__PURE__ */ x("p", {
|
|
115
|
+
className: "mt-1 truncate text-sm font-bold",
|
|
116
|
+
children: P.ownerId ? /* @__PURE__ */ x(t, { id: P.ownerId }) : O("planmagnet.productFeatures.fields.noOwner", "No owner")
|
|
117
|
+
})]
|
|
118
|
+
}) }),
|
|
119
|
+
/* @__PURE__ */ x(w, { children: /* @__PURE__ */ S(T, {
|
|
120
|
+
className: "pt-4",
|
|
121
|
+
children: [/* @__PURE__ */ S("div", {
|
|
122
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
123
|
+
children: [/* @__PURE__ */ x(g, { className: "h-4 w-4" }), O("planmagnet.productFeatures.detail.epics", "Epics")]
|
|
124
|
+
}), /* @__PURE__ */ x("p", {
|
|
125
|
+
className: "mt-1 text-xl font-bold",
|
|
126
|
+
children: P.epics.length
|
|
127
|
+
})]
|
|
128
|
+
}) }),
|
|
129
|
+
/* @__PURE__ */ x(w, { children: /* @__PURE__ */ S(T, {
|
|
130
|
+
className: "pt-4",
|
|
131
|
+
children: [/* @__PURE__ */ S("div", {
|
|
132
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
133
|
+
children: [/* @__PURE__ */ x(h, { className: "h-4 w-4" }), O("planmagnet.productFeatures.detail.requests", "Feature requests")]
|
|
134
|
+
}), /* @__PURE__ */ x("p", {
|
|
135
|
+
className: "mt-1 text-xl font-bold",
|
|
136
|
+
children: P.featureRequests.length
|
|
137
|
+
})]
|
|
138
|
+
}) })
|
|
139
|
+
]
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ S(w, { children: [/* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(D, { children: O("planmagnet.productFeatures.fields.description", "Description") }) }), /* @__PURE__ */ S(T, {
|
|
142
|
+
className: "space-y-2",
|
|
143
|
+
children: [/* @__PURE__ */ x("p", {
|
|
144
|
+
className: "whitespace-pre-wrap text-sm",
|
|
145
|
+
children: P.description || O("planmagnet.productFeatures.detail.noDescription", "No description yet.")
|
|
146
|
+
}), /* @__PURE__ */ S("p", {
|
|
147
|
+
className: "text-xs text-muted-foreground",
|
|
148
|
+
children: [
|
|
149
|
+
L && O("planmagnet.productFeatures.detail.created", "Created {{date}}", { date: L.toLocaleDateString() }),
|
|
150
|
+
L && I && " · ",
|
|
151
|
+
I && O("planmagnet.productFeatures.detail.updated", "Updated {{date}}", { date: I.toLocaleDateString() })
|
|
152
|
+
]
|
|
153
|
+
})]
|
|
154
|
+
})] }),
|
|
155
|
+
/* @__PURE__ */ S("div", {
|
|
156
|
+
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
157
|
+
children: [/* @__PURE__ */ S(w, { children: [/* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(D, { children: O("planmagnet.productFeatures.detail.epicsTitle", "Epics and stories") }) }), /* @__PURE__ */ x(T, { children: /* @__PURE__ */ x(d, {
|
|
158
|
+
featureId: P.id,
|
|
159
|
+
archived: F,
|
|
160
|
+
epics: P.epics
|
|
161
|
+
}) })] }), /* @__PURE__ */ S(w, { children: [/* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(D, { children: O("planmagnet.productFeatures.detail.requestsTitle", "Feature requests") }) }), /* @__PURE__ */ x(T, { children: /* @__PURE__ */ x(f, {
|
|
162
|
+
featureId: P.id,
|
|
163
|
+
productId: P.productId,
|
|
164
|
+
archived: F,
|
|
165
|
+
requests: P.featureRequests
|
|
166
|
+
}) })] })]
|
|
167
|
+
})
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { O as ProductsProductFeatureDetailPagePage };
|
|
173
|
+
|
|
174
|
+
//# sourceMappingURL=ProductsProductFeatureDetailPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductsProductFeatureDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsProductFeatureDetailPagePage.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport { Badge, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { ChevronRight, Loader2, MessageSquare, Milestone, Puzzle, Users } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { PlanMagnetNotFoundPage } from './NotFoundPage';\nimport { PRODUCT_FEATURE_DETAIL } from '../product/hierarchyOperations';\nimport { parseServerDate } from '../product/hierarchy';\nimport {\n ProductFeatureHeaderActions,\n ProductFeatureStatusBadge,\n type ProductFeatureValues,\n} from '../product/ProductFeatureControls';\nimport {\n FeatureEpicsPanel,\n FeatureRequestsPanel,\n type FeatureEpic,\n type FeatureLinkedRequest,\n} from '../product/FeatureLinkPanels';\n\n/**\n * A product feature (PRM-01): a capability the team decided to build, under one product. Edit its name,\n * description, status and owner; archive or delete it; link the EPIC work items that deliver it (each shown with\n * its stories) and the feature requests it answers. Not a feature request — those are customer feedback, and keep\n * their own page.\n */\n\ninterface FeatureDetail extends ProductFeatureValues {\n createdAt: string;\n updatedAt: string;\n product: {\n id: string;\n name: string;\n archivedAt: string | null;\n productLine: { id: string; name: string } | null;\n } | null;\n epics: FeatureEpic[];\n featureRequests: FeatureLinkedRequest[];\n}\n\nexport function ProductsProductFeatureDetailPagePage() {\n const tr = useTr();\n const navigate = usePlanMagnetNavigate();\n const featureId = useEntityId('productFeatureId') ?? '';\n\n const { data, loading, error } = useQuery<{ planmagnetProductFeature: FeatureDetail | null }>(\n PRODUCT_FEATURE_DETAIL,\n { variables: { id: featureId }, skip: !featureId }\n );\n\n if (!featureId) {\n return (\n <div className=\"p-6 text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.detail.noneSelected',\n 'No feature selected. Open one from its product’s page.'\n )}\n </div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it).\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 && !data) {\n return (\n <div className=\"p-6 text-destructive\">\n {tr(\n 'planmagnet.productFeatures.detail.loadError',\n 'Failed to load the feature: {{message}}',\n {\n message: error.message,\n }\n )}\n </div>\n );\n }\n\n const feature = data?.planmagnetProductFeature;\n // Deleted, another workspace's or an unknown id all read back as null — the real 404 page.\n if (!feature) return <PlanMagnetNotFoundPage />;\n\n const archived = !!feature.archivedAt;\n const updated = parseServerDate(feature.updatedAt);\n const created = parseServerDate(feature.createdAt);\n\n return (\n <div className=\"space-y-6 p-6\">\n <nav\n aria-label={tr('planmagnet.productFeatures.detail.breadcrumb', 'Breadcrumb')}\n className=\"flex flex-wrap items-center gap-1 text-xs text-muted-foreground\"\n >\n {feature.product?.productLine && (\n <>\n <button\n type=\"button\"\n className=\"hover:underline\"\n onClick={() => navigate('/products/product-lines-page')}\n >\n {feature.product.productLine.name}\n </button>\n <ChevronRight className=\"h-3 w-3\" />\n </>\n )}\n {feature.product ? (\n <button\n type=\"button\"\n className=\"hover:underline\"\n onClick={() =>\n feature.product &&\n navigate(detailPath('/products/product-detail-page', feature.product.id))\n }\n >\n {feature.product.name}\n </button>\n ) : (\n <span>{tr('planmagnet.productFeatures.detail.productGone', 'Deleted product')}</span>\n )}\n <ChevronRight className=\"h-3 w-3\" />\n <span className=\"text-foreground\">{feature.name}</span>\n </nav>\n\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Puzzle className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{feature.name}</h1>\n <ProductFeatureStatusBadge status={feature.status} />\n {archived && (\n <Badge variant=\"outline\">\n {tr('planmagnet.productFeatures.archived', 'Archived')}\n </Badge>\n )}\n </div>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.detail.subtitle',\n 'A product feature — what the team is building. Feature requests are the feedback it answers.'\n )}\n </p>\n </div>\n <ProductFeatureHeaderActions\n feature={feature}\n epicCount={feature.epics.length}\n requestCount={feature.featureRequests.length}\n />\n </div>\n\n {error && (\n <ErrorLine\n message={tr(\n 'planmagnet.productFeatures.detail.refreshError',\n 'The feature could not be refreshed: {{message}}',\n { message: error.message }\n )}\n />\n )}\n {archived && (\n <p className=\"rounded-md bg-muted p-3 text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.detail.archivedNote',\n 'This feature is archived. It keeps its epics and requests, but takes no new links until it is unarchived.'\n )}\n </p>\n )}\n\n <div className=\"grid grid-cols-2 gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">\n {tr('planmagnet.productFeatures.fields.status', 'Status')}\n </p>\n <div className=\"mt-1\">\n <ProductFeatureStatusBadge status={feature.status} />\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <Users className=\"h-4 w-4\" />\n {tr('planmagnet.productFeatures.fields.owner', 'Owner')}\n </div>\n <p className=\"mt-1 truncate text-sm font-bold\">\n {feature.ownerId ? (\n <PersonName id={feature.ownerId} />\n ) : (\n tr('planmagnet.productFeatures.fields.noOwner', 'No owner')\n )}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <Milestone className=\"h-4 w-4\" />\n {tr('planmagnet.productFeatures.detail.epics', 'Epics')}\n </div>\n <p className=\"mt-1 text-xl font-bold\">{feature.epics.length}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <MessageSquare className=\"h-4 w-4\" />\n {tr('planmagnet.productFeatures.detail.requests', 'Feature requests')}\n </div>\n <p className=\"mt-1 text-xl font-bold\">{feature.featureRequests.length}</p>\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tr('planmagnet.productFeatures.fields.description', 'Description')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <p className=\"whitespace-pre-wrap text-sm\">\n {feature.description ||\n tr('planmagnet.productFeatures.detail.noDescription', 'No description yet.')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {created &&\n tr('planmagnet.productFeatures.detail.created', 'Created {{date}}', {\n date: created.toLocaleDateString(),\n })}\n {created && updated && ' · '}\n {updated &&\n tr('planmagnet.productFeatures.detail.updated', 'Updated {{date}}', {\n date: updated.toLocaleDateString(),\n })}\n </p>\n </CardContent>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>\n {tr('planmagnet.productFeatures.detail.epicsTitle', 'Epics and stories')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <FeatureEpicsPanel featureId={feature.id} archived={archived} epics={feature.epics} />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>\n {tr('planmagnet.productFeatures.detail.requestsTitle', 'Feature requests')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <FeatureRequestsPanel\n featureId={feature.id}\n productId={feature.productId}\n archived={archived}\n requests={feature.featureRequests}\n />\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4CA,SAAgB,IAAuC;CACrD,IAAM,IAAK,GAAO,EACZ,IAAW,GAAuB,EAClC,IAAY,EAAY,mBAAmB,IAAI,IAE/C,EAAE,SAAM,YAAS,aAAU,EAC/B,GACA;EAAE,WAAW,EAAE,IAAI,GAAW;EAAE,MAAM,CAAC;EAAW,CACnD;AAED,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EACC,kDACA,yDACD;EACG,CAAA;AAKV,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,KAAS,CAAC,EACZ,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EACC,+CACA,2CACA,EACE,SAAS,EAAM,SAChB,CACF;EACG,CAAA;CAIV,IAAM,IAAU,GAAM;AAEtB,KAAI,CAAC,EAAS,QAAO,kBAAC,GAAD,EAA0B,CAAA;CAE/C,IAAM,IAAW,CAAC,CAAC,EAAQ,YACrB,IAAU,EAAgB,EAAQ,UAAU,EAC5C,IAAU,EAAgB,EAAQ,UAAU;AAElD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IACE,cAAY,EAAG,gDAAgD,aAAa;IAC5E,WAAU;cAFZ;KAIG,EAAQ,SAAS,eAChB,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,+BAA+B;gBAEtD,EAAQ,QAAQ,YAAY;MACtB,CAAA,EACT,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CACnC,EAAA,CAAA;KAEJ,EAAQ,UACP,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eACE,EAAQ,WACR,EAAS,EAAW,iCAAiC,EAAQ,QAAQ,GAAG,CAAC;gBAG1E,EAAQ,QAAQ;MACV,CAAA,GAET,kBAAC,QAAD,EAAA,UAAO,EAAG,iDAAiD,kBAAkB,EAAQ,CAAA;KAEvF,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;KACpC,kBAAC,QAAD;MAAM,WAAU;gBAAmB,EAAQ;MAAY,CAAA;KACnD;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;OAC3C,kBAAC,MAAD;QAAI,WAAU;kBAA0B,EAAQ;QAAU,CAAA;OAC1D,kBAAC,GAAD,EAA2B,QAAQ,EAAQ,QAAU,CAAA;OACpD,KACC,kBAAC,GAAD;QAAO,SAAQ;kBACZ,EAAG,uCAAuC,WAAW;QAChD,CAAA;OAEN;SACN,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,8CACA,+FACD;MACC,CAAA,CACA;QACN,kBAAC,GAAD;KACW;KACT,WAAW,EAAQ,MAAM;KACzB,cAAc,EAAQ,gBAAgB;KACtC,CAAA,CACE;;GAEL,KACC,kBAAC,GAAD,EACE,SAAS,EACP,kDACA,mDACA,EAAE,SAAS,EAAM,SAAS,CAC3B,EACD,CAAA;GAEH,KACC,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,kDACA,4GACD;IACC,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,4CAA4C,SAAS;OACvD,CAAA,EACJ,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAA2B,QAAQ,EAAQ,QAAU,CAAA;OACjD,CAAA,CACM;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC5B,EAAG,2CAA2C,QAAQ,CACnD;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ,UACP,kBAAC,GAAD,EAAY,IAAI,EAAQ,SAAW,CAAA,GAEnC,EAAG,6CAA6C,WAAW;OAE3D,CAAA,CACQ;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EAAG,2CAA2C,QAAQ,CACnD;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA0B,EAAQ,MAAM;OAAW,CAAA,CACpD;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA,EACpC,EAAG,8CAA8C,mBAAmB,CACjE;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA0B,EAAQ,gBAAgB;OAAW,CAAA,CAC9D;SACT,CAAA;KACH;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACG,EAAG,iDAAiD,cAAc,EACzD,CAAA,EACD,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAQ,eACP,EAAG,mDAAmD,sBAAsB;KAC5E,CAAA,EACJ,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,KACC,EAAG,6CAA6C,oBAAoB,EAClE,MAAM,EAAQ,oBAAoB,EACnC,CAAC;MACH,KAAW,KAAW;MACtB,KACC,EAAG,6CAA6C,oBAAoB,EAClE,MAAM,EAAQ,oBAAoB,EACnC,CAAC;MACF;OACQ;MACT,EAAA,CAAA;GAEP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACG,EAAG,gDAAgD,oBAAoB,EAC9D,CAAA,EACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAmB,WAAW,EAAQ;KAAc;KAAU,OAAO,EAAQ;KAAS,CAAA,EAC1E,CAAA,CACT,EAAA,CAAA,EACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACG,EAAG,mDAAmD,mBAAmB,EAChE,CAAA,EACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KACE,WAAW,EAAQ;KACnB,WAAW,EAAQ;KACT;KACV,UAAU,EAAQ;KAClB,CAAA,EACU,CAAA,CACT,EAAA,CAAA,CACH;;GACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductsProductHierarchyPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductHierarchyPagePage.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,gCAAgC,4CA2N/C"}
|