@burdenoff/microfe-planmagnet 2026.1002.1 → 2026.1002.3

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 (218) hide show
  1. package/dist/generated/wspace-operations.d.ts +2533 -546
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +5654 -71
  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 +180 -162
  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 +100 -84
  11. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  12. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -1
  13. package/dist/planmagnet/board/BulkActions.js +106 -55
  14. package/dist/planmagnet/board/BulkActions.js.map +1 -1
  15. package/dist/planmagnet/controls/Pickers.d.ts +10 -5
  16. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  17. package/dist/planmagnet/controls/Pickers.js +10 -9
  18. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  19. package/dist/planmagnet/dashboards/DashboardUI.d.ts +0 -4
  20. package/dist/planmagnet/dashboards/DashboardUI.d.ts.map +1 -1
  21. package/dist/planmagnet/dashboards/DashboardUI.js +6 -13
  22. package/dist/planmagnet/dashboards/DashboardUI.js.map +1 -1
  23. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  24. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +70 -59
  25. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  26. package/dist/planmagnet/forecast/ResourceForecastCard.js +1 -1
  27. package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -1
  28. package/dist/planmagnet/index.d.ts +1 -1
  29. package/dist/planmagnet/index.d.ts.map +1 -1
  30. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts +3 -3
  31. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts.map +1 -1
  32. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +26 -19
  33. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js.map +1 -1
  34. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts +2 -2
  35. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts.map +1 -1
  36. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +18 -12
  37. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js.map +1 -1
  38. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts +2 -0
  39. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts.map +1 -0
  40. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js +251 -0
  41. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js.map +1 -0
  42. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  43. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  44. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  45. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  46. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  47. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  48. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  49. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  50. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  51. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +107 -82
  54. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  56. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  57. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  58. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  59. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  60. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  61. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  62. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  63. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  64. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  65. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  66. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  67. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  69. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +116 -114
  72. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  73. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  74. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +174 -159
  75. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  76. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  77. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +159 -145
  78. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  79. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  80. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +161 -143
  81. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  82. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  83. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +160 -156
  84. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  85. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.d.ts.map +1 -1
  86. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +97 -99
  87. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js.map +1 -1
  88. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  89. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  90. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  91. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  92. package/dist/planmagnet/product/ProductControls.js +1 -1
  93. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  94. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  95. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  96. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  97. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  98. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  99. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  100. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  101. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  102. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  103. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  104. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  105. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  106. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  107. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  108. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  109. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  110. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  111. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  112. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  113. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  114. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  115. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  116. package/dist/planmagnet/product/hierarchy.js +173 -0
  117. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  118. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  119. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  120. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  121. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  122. package/dist/planmagnet/product/refusal.d.ts +14 -0
  123. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  124. package/dist/planmagnet/product/refusal.js +10 -0
  125. package/dist/planmagnet/product/refusal.js.map +1 -0
  126. package/dist/planmagnet/product/routes.d.ts +10 -0
  127. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  128. package/dist/planmagnet/product/routes.js +2 -2
  129. package/dist/planmagnet/product/routes.js.map +1 -1
  130. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts +20 -0
  131. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts.map +1 -0
  132. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js +55 -0
  133. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js.map +1 -0
  134. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts +30 -0
  135. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts.map +1 -0
  136. package/dist/planmagnet/satisfaction/SatisfactionFigures.js +282 -0
  137. package/dist/planmagnet/satisfaction/SatisfactionFigures.js.map +1 -0
  138. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts +9 -0
  139. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts.map +1 -0
  140. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js +390 -0
  141. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js.map +1 -0
  142. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts +17 -0
  143. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts.map +1 -0
  144. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js +77 -0
  145. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js.map +1 -0
  146. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts +18 -0
  147. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts.map +1 -0
  148. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js +87 -0
  149. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js.map +1 -0
  150. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts +13 -0
  151. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts.map +1 -0
  152. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js +326 -0
  153. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js.map +1 -0
  154. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts +23 -0
  155. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts.map +1 -0
  156. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js +258 -0
  157. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js.map +1 -0
  158. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts +6 -0
  159. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts.map +1 -0
  160. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js +56 -0
  161. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js.map +1 -0
  162. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts +16 -0
  163. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts.map +1 -0
  164. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js +102 -0
  165. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js.map +1 -0
  166. package/dist/planmagnet/satisfaction/satisfaction.d.ts +154 -0
  167. package/dist/planmagnet/satisfaction/satisfaction.d.ts.map +1 -0
  168. package/dist/planmagnet/satisfaction/satisfaction.js +210 -0
  169. package/dist/planmagnet/satisfaction/satisfaction.js.map +1 -0
  170. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts +108 -0
  171. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts.map +1 -0
  172. package/dist/planmagnet/satisfaction/satisfactionCsv.js +211 -0
  173. package/dist/planmagnet/satisfaction/satisfactionCsv.js.map +1 -0
  174. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts +90 -0
  175. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts.map +1 -0
  176. package/dist/planmagnet/satisfaction/satisfactionForm.js +90 -0
  177. package/dist/planmagnet/satisfaction/satisfactionForm.js.map +1 -0
  178. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts +116 -0
  179. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts.map +1 -0
  180. package/dist/planmagnet/satisfaction/satisfactionOperations.js +86 -0
  181. package/dist/planmagnet/satisfaction/satisfactionOperations.js.map +1 -0
  182. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts +20 -0
  183. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts.map +1 -0
  184. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js +31 -0
  185. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js.map +1 -0
  186. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts +35 -0
  187. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts.map +1 -0
  188. package/dist/planmagnet/skills/RequiredSkillsEditor.js +124 -0
  189. package/dist/planmagnet/skills/RequiredSkillsEditor.js.map +1 -0
  190. package/dist/planmagnet/skills/SkillChips.d.ts +30 -0
  191. package/dist/planmagnet/skills/SkillChips.d.ts.map +1 -0
  192. package/dist/planmagnet/skills/SkillChips.js +57 -0
  193. package/dist/planmagnet/skills/SkillChips.js.map +1 -0
  194. package/dist/planmagnet/skills/SkillCoverageCard.d.ts +24 -0
  195. package/dist/planmagnet/skills/SkillCoverageCard.d.ts.map +1 -0
  196. package/dist/planmagnet/skills/SkillCoverageCard.js +256 -0
  197. package/dist/planmagnet/skills/SkillCoverageCard.js.map +1 -0
  198. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts +10 -0
  199. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts.map +1 -0
  200. package/dist/planmagnet/skills/WorkItemRequiredSkills.js +65 -0
  201. package/dist/planmagnet/skills/WorkItemRequiredSkills.js.map +1 -0
  202. package/dist/planmagnet/skills/skillOperations.d.ts +52 -0
  203. package/dist/planmagnet/skills/skillOperations.d.ts.map +1 -0
  204. package/dist/planmagnet/skills/skillOperations.js +44 -0
  205. package/dist/planmagnet/skills/skillOperations.js.map +1 -0
  206. package/dist/planmagnet/skills/skillTags.d.ts +103 -0
  207. package/dist/planmagnet/skills/skillTags.d.ts.map +1 -0
  208. package/dist/planmagnet/skills/skillTags.js +115 -0
  209. package/dist/planmagnet/skills/skillTags.js.map +1 -0
  210. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts +28 -0
  211. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts.map +1 -0
  212. package/dist/planmagnet/skills/useRequiredSkillsDraft.js +30 -0
  213. package/dist/planmagnet/skills/useRequiredSkillsDraft.js.map +1 -0
  214. package/dist/shared/utils/i18n.d.ts +3 -0
  215. package/dist/shared/utils/i18n.d.ts.map +1 -1
  216. package/dist/shared/utils/i18n.js +1 -1
  217. package/dist/shared/utils/i18n.js.map +1 -1
  218. package/package.json +4 -4
@@ -0,0 +1,373 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { useTr as n } from "../../shared/hooks/useTr.js";
3
+ import { throwOnError as r } from "../utils/mutationResult.js";
4
+ import { ErrorLine as i } from "../controls/ConfirmButton.js";
5
+ import { enumLabel as a } from "../controls/WorkItemControls.js";
6
+ import { detailPath as o } from "../utils/rowNavigation.js";
7
+ import { refusalMessage as s } from "./refusal.js";
8
+ import { LINK_EPIC_TO_PRODUCT_FEATURE as c, LINK_FEATURE_REQUEST_TO_PRODUCT_FEATURE as l, PRODUCT_FEATURE_EPIC_CANDIDATES as u, PRODUCT_FEATURE_QUERIES as d, PRODUCT_FEATURE_REQUEST_CANDIDATES as f, UNLINK_EPIC_FROM_PRODUCT_FEATURE as p, UNLINK_FEATURE_REQUEST_FROM_PRODUCT_FEATURE as m } from "./hierarchyOperations.js";
9
+ import { linkableRequests as h } from "./hierarchy.js";
10
+ import { useDeferredValue as g, useState as _ } from "react";
11
+ import { Link2 as v, ThumbsUp as y, X as b } from "lucide-react";
12
+ import { useMutation as x, useQuery as S } from "@apollo/client/react";
13
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
14
+ import { Badge as T, Button as E, Input as D } from "@burdenoff/fe-libs/ui";
15
+ //#region src/planmagnet/product/FeatureLinkPanels.tsx
16
+ var O = "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60", k = {
17
+ refetchQueries: d,
18
+ awaitRefetchQueries: !0
19
+ };
20
+ function A(e) {
21
+ return `/tasks/task-detail-page?taskId=${encodeURIComponent(e)}`;
22
+ }
23
+ function j() {
24
+ return n()("planmagnet.productFeatures.errors.forbiddenLinks", "Your role cannot change what this feature is linked to.");
25
+ }
26
+ function M({ featureId: e, archived: o, epics: c }) {
27
+ let l = n(), u = j(), d = t(), [f, m] = x(p, k), [h, g] = _(null), [v, y] = _(null), S = async (t) => {
28
+ y(null), g(t);
29
+ try {
30
+ await r(f({ variables: {
31
+ productFeatureId: e,
32
+ workItemId: t
33
+ } }));
34
+ } catch (e) {
35
+ y(s(e, l("planmagnet.productFeatures.epics.errors.unlink", "The epic could not be unlinked."), u));
36
+ } finally {
37
+ g(null);
38
+ }
39
+ };
40
+ return /* @__PURE__ */ w("div", {
41
+ className: "space-y-3",
42
+ "data-testid": "product-feature-epics",
43
+ children: [
44
+ /* @__PURE__ */ C("p", {
45
+ className: "text-sm text-muted-foreground",
46
+ children: l("planmagnet.productFeatures.epics.subtitle", "Epics that deliver this feature. Stories reach it through their epic; unlinking an epic changes nothing about the epic or its stories.")
47
+ }),
48
+ c.length === 0 ? /* @__PURE__ */ C("p", {
49
+ className: "py-2 text-center text-sm text-muted-foreground",
50
+ children: l("planmagnet.productFeatures.epics.empty", "No epics linked to this feature yet.")
51
+ }) : /* @__PURE__ */ C("ul", {
52
+ className: "space-y-2",
53
+ children: c.map((e) => /* @__PURE__ */ w("li", {
54
+ className: "rounded-md border p-2 text-sm",
55
+ "data-testid": "feature-epic",
56
+ children: [/* @__PURE__ */ w("div", {
57
+ className: "flex flex-wrap items-center justify-between gap-2",
58
+ children: [/* @__PURE__ */ w("button", {
59
+ type: "button",
60
+ className: "min-w-0 truncate text-left font-medium hover:underline",
61
+ onClick: () => d(A(e.id)),
62
+ children: [/* @__PURE__ */ C("span", {
63
+ className: "mr-2 text-muted-foreground",
64
+ children: e.key
65
+ }), e.title]
66
+ }), /* @__PURE__ */ w("div", {
67
+ className: "flex shrink-0 items-center gap-2",
68
+ children: [/* @__PURE__ */ C(T, {
69
+ variant: "outline",
70
+ className: "text-xs",
71
+ children: a(e.status)
72
+ }), /* @__PURE__ */ C(E, {
73
+ size: "sm",
74
+ variant: "ghost",
75
+ "aria-label": l("planmagnet.productFeatures.epics.unlinkNamed", "Unlink {{key}}", { key: e.key }),
76
+ disabled: m.loading,
77
+ onClick: () => void S(e.id),
78
+ "data-testid": "feature-epic-unlink",
79
+ children: h === e.id ? "…" : /* @__PURE__ */ C(b, { className: "h-4 w-4" })
80
+ })]
81
+ })]
82
+ }), e.children.length === 0 ? /* @__PURE__ */ C("p", {
83
+ className: "mt-1 pl-4 text-xs text-muted-foreground",
84
+ children: l("planmagnet.productFeatures.epics.noStories", "No stories under this epic yet.")
85
+ }) : /* @__PURE__ */ C("ul", {
86
+ className: "mt-1 space-y-1 border-l pl-4",
87
+ "data-testid": "feature-epic-stories",
88
+ children: e.children.map((e) => /* @__PURE__ */ w("li", {
89
+ className: "flex flex-wrap items-center justify-between gap-x-2 text-xs",
90
+ children: [/* @__PURE__ */ w("button", {
91
+ type: "button",
92
+ className: "min-w-0 truncate text-left hover:underline",
93
+ onClick: () => d(A(e.id)),
94
+ children: [/* @__PURE__ */ C("span", {
95
+ className: "mr-2 text-muted-foreground",
96
+ children: e.key
97
+ }), e.title]
98
+ }), /* @__PURE__ */ w("span", {
99
+ className: "shrink-0 text-muted-foreground",
100
+ children: [
101
+ a(e.type),
102
+ " · ",
103
+ a(e.status)
104
+ ]
105
+ })]
106
+ }, e.id))
107
+ })]
108
+ }, e.id))
109
+ }),
110
+ v && /* @__PURE__ */ C(i, { message: v }),
111
+ o ? /* @__PURE__ */ C("p", {
112
+ className: "text-xs text-muted-foreground",
113
+ children: l("planmagnet.productFeatures.archivedNoLinks", "This feature is archived — unarchive it to link more.")
114
+ }) : /* @__PURE__ */ C(N, {
115
+ featureId: e,
116
+ linkedIds: c.map((e) => e.id)
117
+ })
118
+ ]
119
+ });
120
+ }
121
+ function N({ featureId: t, linkedIds: a }) {
122
+ let o = n(), l = j(), { workspaceId: d } = e(), [f, p] = _(""), m = g(f.trim()), [h, y] = _(""), [b, T] = _(null), { data: A, loading: M, error: N } = S(u, {
123
+ variables: {
124
+ workspaceId: d ?? "",
125
+ filter: {
126
+ type: "EPIC",
127
+ ...m ? { search: m } : {},
128
+ ...a.length ? { excludeIds: a } : {}
129
+ }
130
+ },
131
+ skip: !d
132
+ }), [P, F] = x(c, k), I = A?.planmagnetWorkItems.items ?? [], L = A?.planmagnetWorkItems.totalCount ?? 0, R = I.find((e) => e.id === h) ?? null, z = async () => {
133
+ if (h) {
134
+ T(null);
135
+ try {
136
+ await r(P({ variables: {
137
+ productFeatureId: t,
138
+ workItemId: h
139
+ } })), y(""), p("");
140
+ } catch (e) {
141
+ T(s(e, o("planmagnet.productFeatures.epics.errors.link", "The epic could not be linked."), l));
142
+ }
143
+ }
144
+ };
145
+ return /* @__PURE__ */ w("div", {
146
+ className: "space-y-2 border-t pt-3",
147
+ "data-testid": "feature-epic-link",
148
+ children: [
149
+ /* @__PURE__ */ C("label", {
150
+ htmlFor: `feature-epic-select-${t}`,
151
+ className: "text-sm font-medium",
152
+ children: o("planmagnet.productFeatures.epics.linkLabel", "Link an epic")
153
+ }),
154
+ /* @__PURE__ */ C(D, {
155
+ "aria-label": o("planmagnet.productFeatures.epics.search", "Search epics"),
156
+ placeholder: o("planmagnet.productFeatures.epics.searchPlaceholder", "Search epics by key or title"),
157
+ value: f,
158
+ onChange: (e) => p(e.target.value)
159
+ }),
160
+ /* @__PURE__ */ w("div", {
161
+ className: "flex items-start gap-2",
162
+ children: [/* @__PURE__ */ w("select", {
163
+ id: `feature-epic-select-${t}`,
164
+ className: O,
165
+ value: h,
166
+ onChange: (e) => y(e.target.value),
167
+ "data-testid": "feature-epic-select",
168
+ children: [/* @__PURE__ */ C("option", {
169
+ value: "",
170
+ children: M && !A ? o("planmagnet.productFeatures.searching", "Searching…") : I.length ? o("planmagnet.productFeatures.epics.choose", "Choose an epic") : o("planmagnet.productFeatures.epics.noMatch", "No epic matches")
171
+ }), I.map((e) => /* @__PURE__ */ w("option", {
172
+ value: e.id,
173
+ children: [
174
+ e.key,
175
+ " — ",
176
+ e.title
177
+ ]
178
+ }, e.id))]
179
+ }), /* @__PURE__ */ w(E, {
180
+ size: "sm",
181
+ disabled: !h || F.loading,
182
+ onClick: () => void z(),
183
+ "data-testid": "feature-epic-link-confirm",
184
+ children: [/* @__PURE__ */ C(v, { className: "mr-1 h-4 w-4" }), o("planmagnet.productFeatures.epics.link", "Link")]
185
+ })]
186
+ }),
187
+ R?.productFeature && /* @__PURE__ */ C("p", {
188
+ className: "text-xs text-muted-foreground",
189
+ children: o("planmagnet.productFeatures.epics.moves", "{{key}} is linked to \"{{feature}}\" — an epic belongs to one feature, so linking moves it here.", {
190
+ key: R.key,
191
+ feature: R.productFeature.name
192
+ })
193
+ }),
194
+ N && /* @__PURE__ */ C(i, { message: o("planmagnet.productFeatures.epics.errors.load", "Epics could not be searched: {{message}}", { message: N.message }) }),
195
+ L > I.length && /* @__PURE__ */ C("p", {
196
+ className: "text-xs text-muted-foreground",
197
+ children: o("planmagnet.productFeatures.narrow", "Showing {{shown}} of {{total}} — type to narrow the list.", {
198
+ shown: I.length,
199
+ total: L
200
+ })
201
+ }),
202
+ b && /* @__PURE__ */ C(i, { message: b })
203
+ ]
204
+ });
205
+ }
206
+ function P({ featureId: e, productId: c, archived: l, requests: u }) {
207
+ let d = n(), f = j(), p = t(), [h, g] = x(m, k), [v, S] = _(null), [D, O] = _(null), A = async (t) => {
208
+ O(null), S(t);
209
+ try {
210
+ await r(h({ variables: {
211
+ featureRequestId: t,
212
+ productFeatureId: e
213
+ } }));
214
+ } catch (e) {
215
+ O(s(e, d("planmagnet.productFeatures.requests.errors.unlink", "The feature request could not be unlinked."), f));
216
+ } finally {
217
+ S(null);
218
+ }
219
+ };
220
+ return /* @__PURE__ */ w("div", {
221
+ className: "space-y-3",
222
+ "data-testid": "product-feature-requests",
223
+ children: [
224
+ /* @__PURE__ */ C("p", {
225
+ className: "text-sm text-muted-foreground",
226
+ children: d("planmagnet.productFeatures.requests.subtitle", "Customer and user feedback this feature answers. A request stays a request — linking never converts or changes it.")
227
+ }),
228
+ u.length === 0 ? /* @__PURE__ */ C("p", {
229
+ className: "py-2 text-center text-sm text-muted-foreground",
230
+ children: d("planmagnet.productFeatures.requests.empty", "No feature requests linked to this feature yet.")
231
+ }) : /* @__PURE__ */ C("ul", {
232
+ className: "space-y-1",
233
+ children: u.map((e) => /* @__PURE__ */ w("li", {
234
+ className: "flex flex-wrap items-center justify-between gap-2 rounded-md border p-2 text-sm",
235
+ "data-testid": "feature-request-row",
236
+ children: [/* @__PURE__ */ C("button", {
237
+ type: "button",
238
+ className: "min-w-0 truncate text-left hover:underline",
239
+ onClick: () => p(o("/products/feature-detail-page", e.id)),
240
+ children: e.title
241
+ }), /* @__PURE__ */ w("div", {
242
+ className: "flex shrink-0 items-center gap-2",
243
+ children: [
244
+ /* @__PURE__ */ w("span", {
245
+ className: "flex items-center gap-1 text-xs text-muted-foreground",
246
+ children: [/* @__PURE__ */ C(y, { className: "h-3 w-3" }), e.voteCount]
247
+ }),
248
+ /* @__PURE__ */ C(T, {
249
+ variant: "secondary",
250
+ className: "text-xs",
251
+ children: a(e.status)
252
+ }),
253
+ /* @__PURE__ */ C(E, {
254
+ size: "sm",
255
+ variant: "ghost",
256
+ "aria-label": d("planmagnet.productFeatures.requests.unlinkNamed", "Unlink {{title}}", { title: e.title }),
257
+ disabled: g.loading,
258
+ onClick: () => void A(e.id),
259
+ "data-testid": "feature-request-unlink",
260
+ children: v === e.id ? "…" : /* @__PURE__ */ C(b, { className: "h-4 w-4" })
261
+ })
262
+ ]
263
+ })]
264
+ }, e.id))
265
+ }),
266
+ D && /* @__PURE__ */ C(i, { message: D }),
267
+ l ? /* @__PURE__ */ C("p", {
268
+ className: "text-xs text-muted-foreground",
269
+ children: d("planmagnet.productFeatures.archivedNoLinks", "This feature is archived — unarchive it to link more.")
270
+ }) : /* @__PURE__ */ C(F, {
271
+ featureId: e,
272
+ productId: c,
273
+ linkedIds: u.map((e) => e.id)
274
+ })
275
+ ]
276
+ });
277
+ }
278
+ function F({ featureId: t, productId: a, linkedIds: o }) {
279
+ let c = n(), u = j(), { workspaceId: d } = e(), [p, m] = _(""), y = g(p.trim()), [b, T] = _(""), [A, M] = _(null), { data: N, loading: P, error: F } = S(f, {
280
+ variables: {
281
+ workspaceId: d ?? "",
282
+ sameProduct: {
283
+ productId: a,
284
+ ...y ? { search: y } : {}
285
+ },
286
+ anyProduct: y ? { search: y } : void 0
287
+ },
288
+ skip: !d
289
+ }), [I, L] = x(l, k), R = h(N?.sameProduct.items ?? [], (N?.anyProduct.items ?? []).filter((e) => e.productId === null), a, o), z = R.find((e) => e.id === b) ?? null, B = !!N && (N.sameProduct.totalCount > N.sameProduct.items.length || N.anyProduct.totalCount > N.anyProduct.items.length), V = async () => {
290
+ if (b) {
291
+ M(null);
292
+ try {
293
+ await r(I({ variables: {
294
+ featureRequestId: b,
295
+ productFeatureId: t
296
+ } })), T(""), m("");
297
+ } catch (e) {
298
+ M(s(e, c("planmagnet.productFeatures.requests.errors.link", "The feature request could not be linked."), u));
299
+ }
300
+ }
301
+ }, H = R.filter((e) => e.productId === a), U = R.filter((e) => e.productId === null);
302
+ return /* @__PURE__ */ w("div", {
303
+ className: "space-y-2 border-t pt-3",
304
+ "data-testid": "feature-request-link",
305
+ children: [
306
+ /* @__PURE__ */ C("label", {
307
+ htmlFor: `feature-request-select-${t}`,
308
+ className: "text-sm font-medium",
309
+ children: c("planmagnet.productFeatures.requests.linkLabel", "Link a feature request")
310
+ }),
311
+ /* @__PURE__ */ C(D, {
312
+ "aria-label": c("planmagnet.productFeatures.requests.search", "Search feature requests"),
313
+ placeholder: c("planmagnet.productFeatures.requests.searchPlaceholder", "Search feature requests by title"),
314
+ value: p,
315
+ onChange: (e) => m(e.target.value)
316
+ }),
317
+ /* @__PURE__ */ w("div", {
318
+ className: "flex items-start gap-2",
319
+ children: [/* @__PURE__ */ w("select", {
320
+ id: `feature-request-select-${t}`,
321
+ className: O,
322
+ value: b,
323
+ onChange: (e) => T(e.target.value),
324
+ "data-testid": "feature-request-select",
325
+ children: [
326
+ /* @__PURE__ */ C("option", {
327
+ value: "",
328
+ children: P && !N ? c("planmagnet.productFeatures.searching", "Searching…") : R.length ? c("planmagnet.productFeatures.requests.choose", "Choose a feature request") : c("planmagnet.productFeatures.requests.noMatch", "No feature request matches")
329
+ }),
330
+ H.length > 0 && /* @__PURE__ */ C("optgroup", {
331
+ label: c("planmagnet.productFeatures.requests.groupOwn", "From this product"),
332
+ children: H.map((e) => /* @__PURE__ */ C("option", {
333
+ value: e.id,
334
+ children: e.title
335
+ }, e.id))
336
+ }),
337
+ U.length > 0 && /* @__PURE__ */ C("optgroup", {
338
+ label: c("planmagnet.productFeatures.requests.groupNone", "Without a product"),
339
+ children: U.map((e) => /* @__PURE__ */ C("option", {
340
+ value: e.id,
341
+ children: e.title
342
+ }, e.id))
343
+ })
344
+ ]
345
+ }), /* @__PURE__ */ w(E, {
346
+ size: "sm",
347
+ disabled: !b || L.loading,
348
+ onClick: () => void V(),
349
+ "data-testid": "feature-request-link-confirm",
350
+ children: [/* @__PURE__ */ C(v, { className: "mr-1 h-4 w-4" }), c("planmagnet.productFeatures.requests.link", "Link")]
351
+ })]
352
+ }),
353
+ z?.productFeature && /* @__PURE__ */ C("p", {
354
+ className: "text-xs text-muted-foreground",
355
+ children: c("planmagnet.productFeatures.requests.moves", "This request points at \"{{feature}}\" — a request points at one feature, so linking moves it here.", { feature: z.productFeature.name })
356
+ }),
357
+ z && z.productId === null && /* @__PURE__ */ C("p", {
358
+ className: "text-xs text-muted-foreground",
359
+ children: c("planmagnet.productFeatures.requests.noProduct", "This request has no product. Linking does not set one.")
360
+ }),
361
+ F && /* @__PURE__ */ C(i, { message: c("planmagnet.productFeatures.requests.errors.load", "Feature requests could not be searched: {{message}}", { message: F.message }) }),
362
+ B && /* @__PURE__ */ C("p", {
363
+ className: "text-xs text-muted-foreground",
364
+ children: c("planmagnet.productFeatures.requests.narrow", "Only the first matches are listed — type to narrow the list.")
365
+ }),
366
+ A && /* @__PURE__ */ C(i, { message: A })
367
+ ]
368
+ });
369
+ }
370
+ //#endregion
371
+ export { M as FeatureEpicsPanel, P as FeatureRequestsPanel };
372
+
373
+ //# sourceMappingURL=FeatureLinkPanels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeatureLinkPanels.js","names":[],"sources":["../../../src/planmagnet/product/FeatureLinkPanels.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Badge, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Link2, ThumbsUp, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { throwOnError } from '../utils/mutationResult';\nimport { detailPath } from '../utils/rowNavigation';\nimport { linkableRequests } from './hierarchy';\nimport {\n LINK_EPIC_TO_PRODUCT_FEATURE,\n LINK_FEATURE_REQUEST_TO_PRODUCT_FEATURE,\n PRODUCT_FEATURE_EPIC_CANDIDATES,\n PRODUCT_FEATURE_QUERIES,\n PRODUCT_FEATURE_REQUEST_CANDIDATES,\n UNLINK_EPIC_FROM_PRODUCT_FEATURE,\n UNLINK_FEATURE_REQUEST_FROM_PRODUCT_FEATURE,\n} from './hierarchyOperations';\nimport { refusalMessage } from './refusal';\n\n/**\n * The feature detail page's two link panels (PRM-01): the EPIC work items that deliver the feature (each with the\n * stories under it, through the existing parent/child relation) and the feature requests it answers. Linking and\n * unlinking only ever writes the link — the epic, its stories and the request are never changed — and a link the\n * service refuses (not an epic, another product's request, an archived feature) shows the service's own reason.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst writeOptions = { refetchQueries: PRODUCT_FEATURE_QUERIES, awaitRefetchQueries: true };\n\nfunction taskPath(id: string) {\n return `/tasks/task-detail-page?taskId=${encodeURIComponent(id)}`;\n}\n\nfunction useForbiddenText() {\n const tr = useTr();\n return tr(\n 'planmagnet.productFeatures.errors.forbiddenLinks',\n 'Your role cannot change what this feature is linked to.'\n );\n}\n\n// ─── Epics ──────────────────────────────────────────────────────────────────\n\nexport interface FeatureEpicChild {\n id: string;\n key: string;\n title: string;\n type: string;\n status: string;\n}\n\nexport interface FeatureEpic extends FeatureEpicChild {\n children: FeatureEpicChild[];\n}\n\ninterface EpicCandidate {\n id: string;\n key: string;\n title: string;\n type: string;\n productFeature: { id: string; name: string } | null;\n}\n\nexport function FeatureEpicsPanel({\n featureId,\n archived,\n epics,\n}: {\n featureId: string;\n archived: boolean;\n epics: FeatureEpic[];\n}) {\n const tr = useTr();\n const forbidden = useForbiddenText();\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(UNLINK_EPIC_FROM_PRODUCT_FEATURE, writeOptions);\n const [removingId, setRemovingId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const onUnlink = async (workItemId: string) => {\n setError(null);\n setRemovingId(workItemId);\n try {\n await throwOnError(unlink({ variables: { productFeatureId: featureId, workItemId } }));\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr('planmagnet.productFeatures.epics.errors.unlink', 'The epic could not be unlinked.'),\n forbidden\n )\n );\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-feature-epics\">\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.epics.subtitle',\n 'Epics that deliver this feature. Stories reach it through their epic; unlinking an epic changes nothing about the epic or its stories.'\n )}\n </p>\n {epics.length === 0 ? (\n <p className=\"py-2 text-center text-sm text-muted-foreground\">\n {tr('planmagnet.productFeatures.epics.empty', 'No epics linked to this feature yet.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {epics.map((epic) => (\n <li key={epic.id} className=\"rounded-md border p-2 text-sm\" data-testid=\"feature-epic\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left font-medium hover:underline\"\n onClick={() => navigate(taskPath(epic.id))}\n >\n <span className=\"mr-2 text-muted-foreground\">{epic.key}</span>\n {epic.title}\n </button>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant=\"outline\" className=\"text-xs\">\n {enumLabel(epic.status)}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={tr(\n 'planmagnet.productFeatures.epics.unlinkNamed',\n 'Unlink {{key}}',\n {\n key: epic.key,\n }\n )}\n disabled={unlinkState.loading}\n onClick={() => void onUnlink(epic.id)}\n data-testid=\"feature-epic-unlink\"\n >\n {removingId === epic.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </div>\n {epic.children.length === 0 ? (\n <p className=\"mt-1 pl-4 text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.epics.noStories',\n 'No stories under this epic yet.'\n )}\n </p>\n ) : (\n <ul className=\"mt-1 space-y-1 border-l pl-4\" data-testid=\"feature-epic-stories\">\n {epic.children.map((child) => (\n <li\n key={child.id}\n className=\"flex flex-wrap items-center justify-between gap-x-2 text-xs\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(taskPath(child.id))}\n >\n <span className=\"mr-2 text-muted-foreground\">{child.key}</span>\n {child.title}\n </button>\n <span className=\"shrink-0 text-muted-foreground\">\n {enumLabel(child.type)} · {enumLabel(child.status)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </li>\n ))}\n </ul>\n )}\n {error && <ErrorLine message={error} />}\n {archived ? (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.archivedNoLinks',\n 'This feature is archived — unarchive it to link more.'\n )}\n </p>\n ) : (\n <LinkEpicControl featureId={featureId} linkedIds={epics.map((e) => e.id)} />\n )}\n </div>\n );\n}\n\n/** Search EPIC work items only (the service refuses any other type) and link one. */\nfunction LinkEpicControl({ featureId, linkedIds }: { featureId: string; linkedIds: string[] }) {\n const tr = useTr();\n const forbidden = useForbiddenText();\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const [selectedId, setSelectedId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{\n planmagnetWorkItems: { items: EpicCandidate[]; totalCount: number };\n }>(PRODUCT_FEATURE_EPIC_CANDIDATES, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: {\n type: 'EPIC',\n ...(deferred ? { search: deferred } : {}),\n ...(linkedIds.length ? { excludeIds: linkedIds } : {}),\n },\n },\n skip: !workspaceId,\n });\n const [link, linkState] = useMutation(LINK_EPIC_TO_PRODUCT_FEATURE, writeOptions);\n const candidates = data?.planmagnetWorkItems.items ?? [];\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n const selected = candidates.find((c) => c.id === selectedId) ?? null;\n\n const onLink = async () => {\n if (!selectedId) return;\n setError(null);\n try {\n await throwOnError(\n link({ variables: { productFeatureId: featureId, workItemId: selectedId } })\n );\n setSelectedId('');\n setSearch('');\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr('planmagnet.productFeatures.epics.errors.link', 'The epic could not be linked.'),\n forbidden\n )\n );\n }\n };\n\n return (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"feature-epic-link\">\n <label htmlFor={`feature-epic-select-${featureId}`} className=\"text-sm font-medium\">\n {tr('planmagnet.productFeatures.epics.linkLabel', 'Link an epic')}\n </label>\n <Input\n aria-label={tr('planmagnet.productFeatures.epics.search', 'Search epics')}\n placeholder={tr(\n 'planmagnet.productFeatures.epics.searchPlaceholder',\n 'Search epics by key or title'\n )}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <div className=\"flex items-start gap-2\">\n <select\n id={`feature-epic-select-${featureId}`}\n className={selectClass}\n value={selectedId}\n onChange={(e) => setSelectedId(e.target.value)}\n data-testid=\"feature-epic-select\"\n >\n <option value=\"\">\n {loading && !data\n ? tr('planmagnet.productFeatures.searching', 'Searching…')\n : candidates.length\n ? tr('planmagnet.productFeatures.epics.choose', 'Choose an epic')\n : tr('planmagnet.productFeatures.epics.noMatch', 'No epic matches')}\n </option>\n {candidates.map((c) => (\n <option key={c.id} value={c.id}>\n {c.key} — {c.title}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n disabled={!selectedId || linkState.loading}\n onClick={() => void onLink()}\n data-testid=\"feature-epic-link-confirm\"\n >\n <Link2 className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.productFeatures.epics.link', 'Link')}\n </Button>\n </div>\n {selected?.productFeature && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.epics.moves',\n '{{key}} is linked to \"{{feature}}\" — an epic belongs to one feature, so linking moves it here.',\n { key: selected.key, feature: selected.productFeature.name }\n )}\n </p>\n )}\n {loadError && (\n <ErrorLine\n message={tr(\n 'planmagnet.productFeatures.epics.errors.load',\n 'Epics could not be searched: {{message}}',\n { message: loadError.message }\n )}\n />\n )}\n {total > candidates.length && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.narrow',\n 'Showing {{shown}} of {{total}} — type to narrow the list.',\n { shown: candidates.length, total }\n )}\n </p>\n )}\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n// ─── Feature requests ───────────────────────────────────────────────────────\n\nexport interface FeatureLinkedRequest {\n id: string;\n title: string;\n status: string;\n voteCount: number;\n productId: string | null;\n}\n\ninterface RequestCandidate {\n id: string;\n title: string;\n status: string;\n productId: string | null;\n productFeature: { id: string; name: string } | null;\n}\n\ninterface RequestCandidateList {\n items: RequestCandidate[];\n totalCount: number;\n}\n\nexport function FeatureRequestsPanel({\n featureId,\n productId,\n archived,\n requests,\n}: {\n featureId: string;\n productId: string;\n archived: boolean;\n requests: FeatureLinkedRequest[];\n}) {\n const tr = useTr();\n const forbidden = useForbiddenText();\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(\n UNLINK_FEATURE_REQUEST_FROM_PRODUCT_FEATURE,\n writeOptions\n );\n const [removingId, setRemovingId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const onUnlink = async (featureRequestId: string) => {\n setError(null);\n setRemovingId(featureRequestId);\n try {\n await throwOnError(unlink({ variables: { featureRequestId, productFeatureId: featureId } }));\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr(\n 'planmagnet.productFeatures.requests.errors.unlink',\n 'The feature request could not be unlinked.'\n ),\n forbidden\n )\n );\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-feature-requests\">\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.requests.subtitle',\n 'Customer and user feedback this feature answers. A request stays a request — linking never converts or changes it.'\n )}\n </p>\n {requests.length === 0 ? (\n <p className=\"py-2 text-center text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.requests.empty',\n 'No feature requests linked to this feature yet.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {requests.map((request) => (\n <li\n key={request.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-md border p-2 text-sm\"\n data-testid=\"feature-request-row\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(detailPath('/products/feature-detail-page', request.id))}\n >\n {request.title}\n </button>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n <ThumbsUp className=\"h-3 w-3\" />\n {request.voteCount}\n </span>\n <Badge variant=\"secondary\" className=\"text-xs\">\n {enumLabel(request.status)}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={tr(\n 'planmagnet.productFeatures.requests.unlinkNamed',\n 'Unlink {{title}}',\n { title: request.title }\n )}\n disabled={unlinkState.loading}\n onClick={() => void onUnlink(request.id)}\n data-testid=\"feature-request-unlink\"\n >\n {removingId === request.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n {error && <ErrorLine message={error} />}\n {archived ? (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.archivedNoLinks',\n 'This feature is archived — unarchive it to link more.'\n )}\n </p>\n ) : (\n <LinkRequestControl\n featureId={featureId}\n productId={productId}\n linkedIds={requests.map((r) => r.id)}\n />\n )}\n </div>\n );\n}\n\n/**\n * Search, then link one request. Offered: this product's requests, then requests with no product — the two kinds\n * the service accepts. A request of another product is never offered (the service would refuse it: link it to a\n * feature of its own product instead), and if the service still refuses a link — say the request moved product\n * meanwhile — its reason is shown as is.\n */\nfunction LinkRequestControl({\n featureId,\n productId,\n linkedIds,\n}: {\n featureId: string;\n productId: string;\n linkedIds: string[];\n}) {\n const tr = useTr();\n const forbidden = useForbiddenText();\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const [selectedId, setSelectedId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{\n sameProduct: RequestCandidateList;\n anyProduct: RequestCandidateList;\n }>(PRODUCT_FEATURE_REQUEST_CANDIDATES, {\n variables: {\n workspaceId: workspaceId ?? '',\n sameProduct: { productId, ...(deferred ? { search: deferred } : {}) },\n anyProduct: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const [link, linkState] = useMutation(LINK_FEATURE_REQUEST_TO_PRODUCT_FEATURE, writeOptions);\n const sameProduct = data?.sameProduct.items ?? [];\n // The service has no \"no product\" filter: requests without one come from the workspace-wide search, so\n // only that page is checked — and the note below says when there is more to narrow down.\n const unassigned = (data?.anyProduct.items ?? []).filter((r) => r.productId === null);\n const candidates = linkableRequests(sameProduct, unassigned, productId, linkedIds);\n const selected = candidates.find((c) => c.id === selectedId) ?? null;\n const truncated =\n !!data &&\n (data.sameProduct.totalCount > data.sameProduct.items.length ||\n data.anyProduct.totalCount > data.anyProduct.items.length);\n\n const onLink = async () => {\n if (!selectedId) return;\n setError(null);\n try {\n await throwOnError(\n link({ variables: { featureRequestId: selectedId, productFeatureId: featureId } })\n );\n setSelectedId('');\n setSearch('');\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr(\n 'planmagnet.productFeatures.requests.errors.link',\n 'The feature request could not be linked.'\n ),\n forbidden\n )\n );\n }\n };\n\n const own = candidates.filter((c) => c.productId === productId);\n const none = candidates.filter((c) => c.productId === null);\n\n return (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"feature-request-link\">\n <label htmlFor={`feature-request-select-${featureId}`} className=\"text-sm font-medium\">\n {tr('planmagnet.productFeatures.requests.linkLabel', 'Link a feature request')}\n </label>\n <Input\n aria-label={tr('planmagnet.productFeatures.requests.search', 'Search feature requests')}\n placeholder={tr(\n 'planmagnet.productFeatures.requests.searchPlaceholder',\n 'Search feature requests by title'\n )}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <div className=\"flex items-start gap-2\">\n <select\n id={`feature-request-select-${featureId}`}\n className={selectClass}\n value={selectedId}\n onChange={(e) => setSelectedId(e.target.value)}\n data-testid=\"feature-request-select\"\n >\n <option value=\"\">\n {loading && !data\n ? tr('planmagnet.productFeatures.searching', 'Searching…')\n : candidates.length\n ? tr('planmagnet.productFeatures.requests.choose', 'Choose a feature request')\n : tr('planmagnet.productFeatures.requests.noMatch', 'No feature request matches')}\n </option>\n {own.length > 0 && (\n <optgroup\n label={tr('planmagnet.productFeatures.requests.groupOwn', 'From this product')}\n >\n {own.map((c) => (\n <option key={c.id} value={c.id}>\n {c.title}\n </option>\n ))}\n </optgroup>\n )}\n {none.length > 0 && (\n <optgroup\n label={tr('planmagnet.productFeatures.requests.groupNone', 'Without a product')}\n >\n {none.map((c) => (\n <option key={c.id} value={c.id}>\n {c.title}\n </option>\n ))}\n </optgroup>\n )}\n </select>\n <Button\n size=\"sm\"\n disabled={!selectedId || linkState.loading}\n onClick={() => void onLink()}\n data-testid=\"feature-request-link-confirm\"\n >\n <Link2 className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.productFeatures.requests.link', 'Link')}\n </Button>\n </div>\n {selected?.productFeature && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.requests.moves',\n 'This request points at \"{{feature}}\" — a request points at one feature, so linking moves it here.',\n { feature: selected.productFeature.name }\n )}\n </p>\n )}\n {selected && selected.productId === null && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.requests.noProduct',\n 'This request has no product. Linking does not set one.'\n )}\n </p>\n )}\n {loadError && (\n <ErrorLine\n message={tr(\n 'planmagnet.productFeatures.requests.errors.load',\n 'Feature requests could not be searched: {{message}}',\n { message: loadError.message }\n )}\n />\n )}\n {truncated && (\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.productFeatures.requests.narrow',\n 'Only the first matches are listed — type to narrow the list.'\n )}\n </p>\n )}\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -76,7 +76,7 @@ function L({ productId: e, name: n, archivedAt: s }) {
76
76
  variant: "outline",
77
77
  icon: /* @__PURE__ */ x(h, { className: "mr-1 h-4 w-4" }),
78
78
  title: `Delete ${n}?`,
79
- description: "Every linked roadmap, release and feature request stays intact — only its link to this product is cleared. This cannot be undone here.",
79
+ description: "Its features are deleted with it. Every linked roadmap, release, feature request and work item stays intact — only its link to this product or to its features is cleared — and removing its teams changes no one's access. This cannot be undone here.",
80
80
  confirmLabel: "Delete product",
81
81
  destructive: !0,
82
82
  disabled: p.loading,