@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.
Files changed (95) hide show
  1. package/dist/generated/wspace-operations.d.ts +1814 -401
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +5167 -54
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/index.js +8 -8
  6. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  7. package/dist/planmagnet/PlanMagnetRoot.js +172 -160
  8. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  10. package/dist/planmagnet/PlanMagnetRoutes.js +95 -83
  11. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  12. package/dist/planmagnet/controls/Pickers.d.ts +7 -2
  13. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  14. package/dist/planmagnet/controls/Pickers.js +10 -9
  15. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  16. package/dist/planmagnet/index.d.ts +1 -1
  17. package/dist/planmagnet/index.d.ts.map +1 -1
  18. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  19. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  20. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  21. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  22. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  23. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  24. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  25. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  26. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  27. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  28. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  29. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +105 -82
  30. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  31. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  32. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  33. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  34. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  35. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  36. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  37. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  38. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  39. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  40. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  41. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  42. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  43. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  45. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +156 -154
  48. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  49. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  50. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  51. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  52. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  53. package/dist/planmagnet/product/ProductControls.js +1 -1
  54. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  55. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  56. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  57. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  58. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  59. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  60. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  61. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  62. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  63. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  64. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  65. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  66. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  67. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  68. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  69. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  70. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  71. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  72. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  73. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  74. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  75. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  76. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  77. package/dist/planmagnet/product/hierarchy.js +173 -0
  78. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  79. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  80. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  81. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  82. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  83. package/dist/planmagnet/product/refusal.d.ts +14 -0
  84. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  85. package/dist/planmagnet/product/refusal.js +10 -0
  86. package/dist/planmagnet/product/refusal.js.map +1 -0
  87. package/dist/planmagnet/product/routes.d.ts +4 -0
  88. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  89. package/dist/planmagnet/product/routes.js +2 -2
  90. package/dist/planmagnet/product/routes.js.map +1 -1
  91. package/dist/shared/utils/i18n.d.ts +3 -0
  92. package/dist/shared/utils/i18n.d.ts.map +1 -1
  93. package/dist/shared/utils/i18n.js +1 -1
  94. package/dist/shared/utils/i18n.js.map +1 -1
  95. 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 { Calendar as p, Loader2 as m, Package as h, Pencil as g, Target as _, Users as v } from "lucide-react";
9
- import { gql as y } from "@apollo/client";
10
- import { useQuery as b } from "@apollo/client/react";
11
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
12
- import { Badge as C, Button as w, Card as T, CardContent as E, CardHeader as D, CardTitle as O } from "@burdenoff/fe-libs/ui";
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 k = y`
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 A() {
70
- let y = e(), A = i("productId") ?? "", { data: j, loading: M, error: N } = b(k, {
71
- variables: { id: A },
72
- skip: !A
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 (!A) return /* @__PURE__ */ x("div", {
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 (M && !j) return /* @__PURE__ */ x("div", {
81
+ if (I && !F) return /* @__PURE__ */ T("div", {
79
82
  className: "flex items-center justify-center p-12",
80
- children: /* @__PURE__ */ x(m, { className: "h-8 w-8 animate-spin text-muted-foreground" })
83
+ children: /* @__PURE__ */ T(v, { className: "h-8 w-8 animate-spin text-muted-foreground" })
81
84
  });
82
- if (N) return /* @__PURE__ */ S("div", {
85
+ if (L) return /* @__PURE__ */ E("div", {
83
86
  className: "p-6 text-destructive",
84
- children: ["Failed to load product: ", N.message]
87
+ children: ["Failed to load product: ", L.message]
85
88
  });
86
- let P = j?.planmagnetProduct;
87
- return P ? /* @__PURE__ */ S("div", {
89
+ let R = F?.planmagnetProduct;
90
+ return R ? /* @__PURE__ */ E("div", {
88
91
  className: "space-y-6 p-6",
89
92
  children: [
90
- /* @__PURE__ */ S("div", {
93
+ /* @__PURE__ */ E("div", {
91
94
  className: "flex items-start justify-between gap-4",
92
- children: [/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ S("div", {
95
+ children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ E("div", {
93
96
  className: "flex items-center gap-2",
94
97
  children: [
95
- /* @__PURE__ */ x(h, { className: "h-5 w-5 text-primary" }),
96
- /* @__PURE__ */ x("h1", {
98
+ /* @__PURE__ */ T(y, { className: "h-5 w-5 text-primary" }),
99
+ /* @__PURE__ */ T("h1", {
97
100
  className: "text-2xl font-semibold",
98
- children: P.name
101
+ children: R.name
99
102
  }),
100
- P.archivedAt && /* @__PURE__ */ x(C, {
103
+ R.archivedAt && /* @__PURE__ */ T(D, {
101
104
  variant: "outline",
102
105
  children: "Archived"
103
106
  }),
104
- /* @__PURE__ */ x(C, {
107
+ /* @__PURE__ */ T(D, {
105
108
  variant: "secondary",
106
109
  className: "capitalize",
107
- children: n(P.lifecycleStage)
110
+ children: n(R.lifecycleStage)
108
111
  })
109
112
  ]
110
- }), /* @__PURE__ */ x("p", {
113
+ }), /* @__PURE__ */ T("p", {
111
114
  className: "text-sm text-muted-foreground mt-1",
112
- children: P.vision || "No vision set"
113
- })] }), /* @__PURE__ */ x("div", {
115
+ children: R.vision || "No vision set"
116
+ })] }), /* @__PURE__ */ T("div", {
114
117
  className: "flex flex-col items-end gap-2",
115
- children: /* @__PURE__ */ S("div", {
118
+ children: /* @__PURE__ */ E("div", {
116
119
  className: "flex gap-2",
117
- children: [/* @__PURE__ */ S(w, {
120
+ children: [/* @__PURE__ */ E(O, {
118
121
  size: "sm",
119
122
  variant: "outline",
120
- onClick: () => y(r("/products/edit-product-page", P.id)),
121
- children: [/* @__PURE__ */ x(g, { className: "mr-1 h-4 w-4" }), "Edit"]
122
- }), /* @__PURE__ */ x(l, {
123
- productId: P.id,
124
- name: P.name,
125
- archivedAt: P.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__ */ S("div", {
133
+ /* @__PURE__ */ E("div", {
131
134
  className: "grid grid-cols-2 md:grid-cols-4 gap-4",
132
135
  children: [
133
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
136
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
134
137
  className: "pt-4",
135
- children: [/* @__PURE__ */ S("div", {
138
+ children: [/* @__PURE__ */ E("div", {
136
139
  className: "flex items-center gap-2 text-muted-foreground text-xs",
137
- children: [/* @__PURE__ */ x(_, { className: "h-4 w-4" }), "North Star"]
138
- }), P.northStar ? /* @__PURE__ */ S("p", {
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
- P.northStar.current,
144
+ R.northStar.current,
142
145
  " / ",
143
- P.northStar.target,
146
+ R.northStar.target,
144
147
  " ",
145
- P.northStar.unit,
146
- P.northStar.progressRatio == null ? "" : ` (${Math.round(P.northStar.progressRatio * 100)}%)`
148
+ R.northStar.unit,
149
+ R.northStar.progressRatio == null ? "" : ` (${Math.round(R.northStar.progressRatio * 100)}%)`
147
150
  ]
148
- }) : /* @__PURE__ */ x("p", {
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__ */ x(T, { children: /* @__PURE__ */ S(E, {
156
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
154
157
  className: "pt-4",
155
- children: [/* @__PURE__ */ S("div", {
158
+ children: [/* @__PURE__ */ E("div", {
156
159
  className: "flex items-center gap-2 text-muted-foreground text-xs",
157
- children: [/* @__PURE__ */ x(v, { className: "h-4 w-4" }), "Owner"]
158
- }), /* @__PURE__ */ x("p", {
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__ */ x(t, { id: P.ownerId })
163
+ children: /* @__PURE__ */ T(t, { id: R.ownerId })
161
164
  })]
162
165
  }) }),
163
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
166
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
164
167
  className: "pt-4",
165
- children: [/* @__PURE__ */ S("div", {
168
+ children: [/* @__PURE__ */ E("div", {
166
169
  className: "flex items-center gap-2 text-muted-foreground text-xs",
167
- children: [/* @__PURE__ */ x(p, { className: "h-4 w-4" }), "Created"]
168
- }), /* @__PURE__ */ x("p", {
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(P.createdAt).toLocaleDateString()
173
+ children: new Date(R.createdAt).toLocaleDateString()
171
174
  })]
172
175
  }) }),
173
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
176
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
174
177
  className: "pt-4",
175
- children: [/* @__PURE__ */ S("div", {
178
+ children: [/* @__PURE__ */ E("div", {
176
179
  className: "flex items-center gap-2 text-muted-foreground text-xs",
177
- children: [/* @__PURE__ */ x(p, { className: "h-4 w-4" }), "Updated"]
178
- }), /* @__PURE__ */ x("p", {
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(P.updatedAt).toLocaleDateString()
183
+ children: new Date(R.updatedAt).toLocaleDateString()
181
184
  })]
182
185
  }) })
183
186
  ]
184
187
  }),
185
- P.description && /* @__PURE__ */ x(T, { children: /* @__PURE__ */ x(E, {
188
+ R.description && /* @__PURE__ */ T(k, { children: /* @__PURE__ */ T(A, {
186
189
  className: "pt-4 text-sm text-muted-foreground",
187
- children: P.description
190
+ children: R.description
188
191
  }) }),
189
- /* @__PURE__ */ S("div", {
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__ */ x(o, { product: P }), /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Competitive landscape" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(s, {
192
- productId: P.id,
193
- competitors: P.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__ */ S("div", {
219
+ /* @__PURE__ */ E("div", {
197
220
  className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
198
221
  children: [
199
- /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Roadmaps" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(f, {
200
- productId: P.id,
201
- roadmaps: P.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__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Releases" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(d, {
204
- productId: P.id,
205
- releases: P.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__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Feature Requests" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(c, {
208
- productId: P.id,
209
- featureRequests: P.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__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "OKR Objectives" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(u, {
212
- productId: P.id,
213
- objectives: P.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__ */ x(a, {});
241
+ }) : /* @__PURE__ */ T(a, {});
219
242
  }
220
243
  //#endregion
221
- export { A as ProductsProductDetailPagePage };
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":";;;;;;;;;;;;;AAkCA,IAAM,IAAqB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuF9B,SAAgB,IAAgC;CAC9C,IAAM,IAAW,GAAuB,EAClC,IAAY,EAAY,YAAY,IAAI,IAExC,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;YAA4B;EAAkD,CAAA;AAMjG,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,4BAAyB,EAAM,QAAc;;CAG5F,IAAM,IAAU,GAAM;AAKtB,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA;MAC5C,kBAAC,MAAD;OAAI,WAAU;iBAA0B,EAAQ;OAAU,CAAA;MACzD,EAAQ,cAAc,kBAAC,GAAD;OAAO,SAAQ;iBAAU;OAAgB,CAAA;MAChE,kBAAC,GAAD;OAAO,SAAQ;OAAY,WAAU;iBAClC,EAAU,EAAQ,eAAe;OAC5B,CAAA;MACJ;QACN,kBAAC,KAAD;KAAG,WAAU;eAAsC,EAAQ,UAAU;KAAoB,CAAA,CACrF,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,eAAe,EAAS,EAAW,+BAA+B,EAAQ,GAAG,CAAC;iBAHhF,CAKE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;UACT,kBAAC,GAAD;OACE,WAAW,EAAQ;OACnB,MAAM,EAAQ;OACd,YAAY,EAAQ;OACpB,CAAA,CACE;;KACF,CAAA,CACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,aAE1B;UACL,EAAQ,YACP,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAQ,UAAU;QAAQ;QAAI,EAAQ,UAAU;QAAO;QAAE,EAAQ,UAAU;QAC3E,EAAQ,UAAU,iBAAiB,OAEhC,KADA,KAAK,KAAK,MAAM,EAAQ,UAAU,gBAAgB,IAAI,CAAC;QAEzD;WAEJ,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAAW,CAAA,CAE/D;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,EAAA,QAEzB;UACN,kBAAC,KAAD;OAAG,WAAU;iBACX,kBAAC,GAAD,EAAY,IAAI,EAAQ,SAAW,CAAA;OACjC,CAAA,CACQ;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,UAE5B;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,IAAI,KAAK,EAAQ,UAAU,CAAC,oBAAoB;OAC/C,CAAA,CACQ;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,UAE5B;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,IAAI,KAAK,EAAQ,UAAU,CAAC,oBAAoB;OAC/C,CAAA,CACQ;SACT,CAAA;KACH;;GAEL,EAAQ,eACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAa,WAAU;cACpB,EAAQ;IACG,CAAA,EACT,CAAA;GAIT,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAA4B,YAAW,CAAA,EACvC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,yBAAiC,CAAA,EACjC,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAyB,WAAW,EAAQ;KAAI,aAAa,EAAQ;KAAe,CAAA,EACxE,CAAA,CACT,EAAA,CAAA,CACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAsB,WAAW,EAAQ;MAAI,UAAU,EAAQ;MAAY,CAAA,EAC/D,CAAA,CACT,EAAA,CAAA;KACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAsB,WAAW,EAAQ;MAAI,UAAU,EAAQ;MAAY,CAAA,EAC/D,CAAA,CACT,EAAA,CAAA;KACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,oBAA4B,CAAA,EAC5B,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MACE,WAAW,EAAQ;MACnB,iBAAiB,EAAQ;MACzB,CAAA,EACU,CAAA,CACT,EAAA,CAAA;KACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,kBAA0B,CAAA,EAC1B,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAwB,WAAW,EAAQ;MAAI,YAAY,EAAQ;MAAc,CAAA,EACrE,CAAA,CACT,EAAA,CAAA;KACH;;GACF;MAnJa,kBAAC,GAAD,EAA0B,CAAA"}
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,2 @@
1
+ export declare function ProductsProductFeatureDetailPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProductsProductFeatureDetailPagePage.d.ts.map
@@ -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":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProductsProductHierarchyPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProductsProductHierarchyPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductsProductHierarchyPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductHierarchyPagePage.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,gCAAgC,4CA2N/C"}