@burdenoff/microfe-planmagnet 2026.927.4 → 2026.927.6

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 (141) hide show
  1. package/dist/generated/global-types.d.ts +9 -1
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.d.ts +0 -12
  4. package/dist/generated/wspace-operations.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  6. package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
  7. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  8. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  9. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
  10. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  11. package/dist/planmagnet/board/ProjectBoard.d.ts +2 -0
  12. package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
  13. package/dist/planmagnet/board/ProjectBoard.js +492 -225
  14. package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
  15. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  16. package/dist/planmagnet/controls/FeatureControls.js +142 -139
  17. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  18. package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
  19. package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
  20. package/dist/planmagnet/controls/FeatureTriage.js +398 -0
  21. package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
  22. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
  23. package/dist/planmagnet/controls/MilestoneControls.js +76 -73
  24. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
  25. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  26. package/dist/planmagnet/controls/ProjectControls.js +47 -44
  27. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  28. package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
  29. package/dist/planmagnet/controls/ReleaseControls.js +47 -44
  30. package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
  31. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  32. package/dist/planmagnet/controls/WorkItemControls.js +68 -65
  33. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  34. package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
  35. package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
  36. package/dist/planmagnet/hooks/useCounts.js +209 -0
  37. package/dist/planmagnet/hooks/useCounts.js.map +1 -0
  38. package/dist/planmagnet/import/exportColumns.d.ts +52 -0
  39. package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
  40. package/dist/planmagnet/import/exportColumns.js +81 -0
  41. package/dist/planmagnet/import/exportColumns.js.map +1 -0
  42. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  43. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
  44. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  45. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  46. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
  47. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  48. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
  50. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
  53. package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  55. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
  56. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  57. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  58. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
  59. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  60. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
  61. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
  62. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  63. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  64. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
  65. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  66. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  67. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
  68. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  69. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  70. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
  71. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  72. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  73. package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
  74. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  75. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  76. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
  77. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
  79. package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
  80. package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
  81. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
  82. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
  83. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
  84. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  85. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
  86. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  87. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
  88. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
  89. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
  90. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
  91. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  92. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
  93. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  94. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
  95. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  96. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
  97. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  98. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  99. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +152 -139
  100. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  101. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  102. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +75 -74
  103. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  104. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
  105. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  106. package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
  107. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  108. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  109. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
  110. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  111. package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
  112. package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
  113. package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
  114. package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
  115. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  116. package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
  117. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  118. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  119. package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
  120. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  121. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  122. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
  123. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  124. package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
  125. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
  126. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
  127. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
  128. package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
  129. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
  130. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  131. package/dist/planmagnet/sprints/SprintControls.js +32 -30
  132. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  133. package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
  134. package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
  135. package/dist/planmagnet/utils/mutationResult.js +2 -1
  136. package/dist/planmagnet/utils/mutationResult.js.map +1 -1
  137. package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
  138. package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
  139. package/dist/planmagnet/utils/rowNavigation.js +9 -8
  140. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  141. package/package.json +1 -1
@@ -1,18 +1,18 @@
1
1
  import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
- import { throwOnError as t } from "../utils/mutationResult.js";
3
- import { ProjectSelect as n, WorkItemSearchSelect as r } from "./Pickers.js";
4
- import { ConfirmButton as i, ErrorLine as a } from "./ConfirmButton.js";
5
- import { parseIntField as o } from "../utils/formValidation.js";
6
- import { enumLabel as s } from "./WorkItemControls.js";
7
- import { detailPath as c } from "../utils/rowNavigation.js";
8
- import { useState as l } from "react";
9
- import { BarChart3 as u, Pencil as d, ThumbsUp as f, Trash2 as p } from "lucide-react";
10
- import { gql as m } from "@apollo/client";
11
- import { useMutation as h } from "@apollo/client/react";
12
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
13
- import { Button as v, Input as y, ResponsiveDialog as b, ResponsiveDialogContent as x, ResponsiveDialogDescription as S, ResponsiveDialogFooter as C, ResponsiveDialogHeader as w, ResponsiveDialogTitle as T, ResponsiveDialogTrigger as E } from "@burdenoff/fe-libs/ui";
2
+ import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
3
+ import { detailPath as r } from "../utils/rowNavigation.js";
4
+ import { ProjectSelect as i, WorkItemSearchSelect as a } from "./Pickers.js";
5
+ import { ConfirmButton as o, ErrorLine as s } from "./ConfirmButton.js";
6
+ import { parseIntField as c } from "../utils/formValidation.js";
7
+ import { enumLabel as l } from "./WorkItemControls.js";
8
+ import { useState as u } from "react";
9
+ import { BarChart3 as d, Pencil as f, ThumbsUp as p, Trash2 as m } from "lucide-react";
10
+ import { gql as h } from "@apollo/client";
11
+ import { useMutation as g } from "@apollo/client/react";
12
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
13
+ import { Button as y, Input as b, ResponsiveDialog as x, ResponsiveDialogContent as S, ResponsiveDialogDescription as C, ResponsiveDialogFooter as w, ResponsiveDialogHeader as T, ResponsiveDialogTitle as E, ResponsiveDialogTrigger as D } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/planmagnet/controls/FeatureControls.tsx
15
- var D = [
15
+ var O = [
16
16
  "SUBMITTED",
17
17
  "UNDER_REVIEW",
18
18
  "PLANNED",
@@ -20,7 +20,7 @@ var D = [
20
20
  "COMPLETED",
21
21
  "DECLINED",
22
22
  "DUPLICATE"
23
- ], O = [
23
+ ], k = [
24
24
  "GetFeatureRequest",
25
25
  "ListFeatureRequests",
26
26
  "ListFeatureRequestsForProduct",
@@ -28,17 +28,17 @@ var D = [
28
28
  "ListAllFeaturesAnalytics",
29
29
  "GetFeatureRequestAnalytics",
30
30
  "ReleaseCustomerRequests"
31
- ], k = m`
31
+ ], A = h`
32
32
  mutation FeatureControlsVote($id: ID!) {
33
33
  planmagnetVoteOnFeature(featureRequestId: $id) {
34
34
  id
35
35
  }
36
36
  }
37
- `, A = m`
37
+ `, j = h`
38
38
  mutation FeatureControlsUnvote($id: ID!) {
39
39
  planmagnetRemoveFeatureVote(featureRequestId: $id)
40
40
  }
41
- `, j = m`
41
+ `, M = h`
42
42
  mutation FeatureControlsUpdate($id: ID!, $input: PlanmagnetUpdateFeatureRequestInput!) {
43
43
  planmagnetUpdateFeatureRequest(id: $id, input: $input) {
44
44
  id
@@ -51,260 +51,263 @@ var D = [
51
51
  workItemId
52
52
  }
53
53
  }
54
- `, M = m`
54
+ `, N = h`
55
55
  mutation FeatureControlsDelete($id: ID!) {
56
56
  planmagnetDeleteFeatureRequest(id: $id)
57
57
  }
58
- `, N = {
59
- refetchQueries: O,
58
+ `, P = {
59
+ refetchQueries: k,
60
60
  awaitRefetchQueries: !0
61
61
  };
62
- function P({ featureId: e, voted: n }) {
63
- let [r, i] = h(k, N), [o, s] = h(A, N), [c, u] = l(null), d = i.loading || s.loading, p = async () => {
64
- u(null);
62
+ function F({ featureId: e, voted: t }) {
63
+ let [r, i] = g(A, P), [a, o] = g(j, P), [c, l] = u(null), d = i.loading || o.loading, f = async () => {
64
+ l(null);
65
65
  try {
66
- await t((n ? o : r)({ variables: { id: e } }));
66
+ await n((t ? a : r)({ variables: { id: e } }));
67
67
  } catch (e) {
68
- u(e instanceof Error ? e.message : "Your vote could not be saved.");
68
+ l(e instanceof Error ? e.message : "Your vote could not be saved.");
69
69
  }
70
70
  };
71
- return /* @__PURE__ */ _("div", {
71
+ return /* @__PURE__ */ v("div", {
72
72
  className: "flex flex-col items-end gap-1",
73
- children: [/* @__PURE__ */ _(v, {
73
+ children: [/* @__PURE__ */ v(y, {
74
74
  size: "sm",
75
- variant: n ? "default" : "outline",
75
+ variant: t ? "default" : "outline",
76
76
  "data-testid": "feature-vote",
77
- "aria-pressed": n,
77
+ "aria-pressed": t,
78
78
  disabled: d,
79
- onClick: () => void p(),
80
- children: [/* @__PURE__ */ g(f, { className: "mr-1 h-4 w-4" }), n ? "Voted" : "Vote"]
81
- }), c && /* @__PURE__ */ g(a, { message: c })]
79
+ onClick: () => void f(),
80
+ children: [/* @__PURE__ */ _(p, { className: "mr-1 h-4 w-4" }), t ? "Voted" : "Vote"]
81
+ }), c && /* @__PURE__ */ _(s, { message: c })]
82
82
  });
83
83
  }
84
- function F({ feature: n }) {
85
- let r = e(), [o, d] = h(j, N), [f, m] = h(M, { refetchQueries: O.filter((e) => e !== "GetFeatureRequest") }), [y, b] = l(null), x = d.loading || m.loading, S = D.includes(n.status) ? D : [n.status, ...D], C = async (e) => {
86
- b(null);
84
+ function I({ feature: i }) {
85
+ let a = e(), [c, f] = g(M, P), [p, h] = g(N, {
86
+ refetchQueries: k.filter((e) => e !== "GetFeatureRequest"),
87
+ context: t
88
+ }), [b, x] = u(null), S = f.loading || h.loading, C = O.includes(i.status) ? O : [i.status, ...O], w = async (e) => {
89
+ x(null);
87
90
  try {
88
- await t(o({ variables: {
89
- id: n.id,
91
+ await n(c({ variables: {
92
+ id: i.id,
90
93
  input: { status: e }
91
94
  } }));
92
95
  } catch (e) {
93
- b(e instanceof Error ? e.message : "The status could not be changed.");
96
+ x(e instanceof Error ? e.message : "The status could not be changed.");
94
97
  }
95
- }, w = async () => {
96
- b(null);
98
+ }, T = async () => {
99
+ x(null);
97
100
  try {
98
- await t(f({ variables: { id: n.id } })), r("/products/features-page");
101
+ await n(p({ variables: { id: i.id } })), a("/products/features-page");
99
102
  } catch (e) {
100
- b(e instanceof Error ? e.message : "The request could not be deleted.");
103
+ x(e instanceof Error ? e.message : "The request could not be deleted.");
101
104
  }
102
105
  };
103
- return /* @__PURE__ */ _("div", {
106
+ return /* @__PURE__ */ v("div", {
104
107
  className: "flex flex-col items-end gap-2",
105
- children: [/* @__PURE__ */ _("div", {
108
+ children: [/* @__PURE__ */ v("div", {
106
109
  className: "flex flex-wrap items-center justify-end gap-2",
107
110
  children: [
108
- /* @__PURE__ */ g("select", {
111
+ /* @__PURE__ */ _("select", {
109
112
  "aria-label": "Status",
110
113
  "data-testid": "feature-status",
111
114
  className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
112
- value: n.status,
113
- disabled: x,
115
+ value: i.status,
116
+ disabled: S,
114
117
  onChange: (e) => {
115
- e.target.value !== n.status && C(e.target.value);
118
+ e.target.value !== i.status && w(e.target.value);
116
119
  },
117
- children: S.map((e) => /* @__PURE__ */ g("option", {
120
+ children: C.map((e) => /* @__PURE__ */ _("option", {
118
121
  value: e,
119
- children: s(e)
122
+ children: l(e)
120
123
  }, e))
121
124
  }),
122
- /* @__PURE__ */ g(I, { feature: n }),
123
- /* @__PURE__ */ _(v, {
125
+ /* @__PURE__ */ _(L, { feature: i }),
126
+ /* @__PURE__ */ v(y, {
124
127
  size: "sm",
125
128
  variant: "outline",
126
129
  "data-testid": "feature-analytics",
127
- onClick: () => r(c("/products/feature-analytics-page", n.id)),
128
- children: [/* @__PURE__ */ g(u, { className: "mr-1 h-4 w-4" }), "Analytics"]
130
+ onClick: () => a(r("/products/feature-analytics-page", i.id)),
131
+ children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Analytics"]
129
132
  }),
130
- /* @__PURE__ */ g(i, {
133
+ /* @__PURE__ */ _(o, {
131
134
  label: "Delete",
132
135
  variant: "outline",
133
- icon: /* @__PURE__ */ g(p, { className: "mr-1 h-4 w-4" }),
134
- title: `Delete "${n.title}"?`,
136
+ icon: /* @__PURE__ */ _(m, { className: "mr-1 h-4 w-4" }),
137
+ title: `Delete "${i.title}"?`,
135
138
  description: "The request and its votes are deleted. This cannot be undone here.",
136
139
  confirmLabel: "Delete request",
137
140
  destructive: !0,
138
- disabled: x,
139
- onConfirm: () => void w()
141
+ disabled: S,
142
+ onConfirm: () => void T()
140
143
  })
141
144
  ]
142
- }), y && /* @__PURE__ */ g(a, { message: y })]
145
+ }), b && /* @__PURE__ */ _(s, { message: b })]
143
146
  });
144
147
  }
145
- function I({ feature: e }) {
146
- let i = {
148
+ function L({ feature: e }) {
149
+ let t = {
147
150
  title: e.title,
148
151
  description: e.description ?? "",
149
152
  businessValue: e.businessValue == null ? "" : String(e.businessValue),
150
153
  effortEstimate: e.effortEstimate ?? "",
151
154
  projectId: e.projectId ?? "",
152
155
  workItemId: e.workItemId ?? ""
153
- }, [s, c] = l(!1), [u, f] = l(i), [p, m] = l(null), [D, O] = h(j, N), k = o(u.businessValue, {
156
+ }, [r, o] = u(!1), [l, d] = u(t), [p, m] = u(null), [h, O] = g(M, P), k = c(l.businessValue, {
154
157
  min: 0,
155
158
  max: 100
156
- }), A = u.title.trim() !== "" && !k.invalid && !O.loading, M = async () => {
159
+ }), A = l.title.trim() !== "" && !k.invalid && !O.loading, j = async () => {
157
160
  m(null);
158
- let n = (e) => e.trim() ? e.trim() : null, r = {
159
- title: u.title.trim(),
160
- description: n(u.description),
161
+ let r = (e) => e.trim() ? e.trim() : null, i = {
162
+ title: l.title.trim(),
163
+ description: r(l.description),
161
164
  businessValue: k.value ?? null,
162
- effortEstimate: n(u.effortEstimate),
163
- projectId: u.projectId || null,
164
- workItemId: u.workItemId || null
165
+ effortEstimate: r(l.effortEstimate),
166
+ projectId: l.projectId || null,
167
+ workItemId: l.workItemId || null
165
168
  }, a = {
166
- title: i.title,
167
- description: n(i.description),
168
- businessValue: i.businessValue === "" ? null : Number(i.businessValue),
169
- effortEstimate: n(i.effortEstimate),
170
- projectId: i.projectId || null,
171
- workItemId: i.workItemId || null
172
- }, o = Object.fromEntries(Object.entries(r).filter(([e, t]) => t !== a[e]));
173
- if (Object.keys(o).length === 0) {
174
- c(!1);
169
+ title: t.title,
170
+ description: r(t.description),
171
+ businessValue: t.businessValue === "" ? null : Number(t.businessValue),
172
+ effortEstimate: r(t.effortEstimate),
173
+ projectId: t.projectId || null,
174
+ workItemId: t.workItemId || null
175
+ }, s = Object.fromEntries(Object.entries(i).filter(([e, t]) => t !== a[e]));
176
+ if (Object.keys(s).length === 0) {
177
+ o(!1);
175
178
  return;
176
179
  }
177
180
  try {
178
- await t(D({ variables: {
181
+ await n(h({ variables: {
179
182
  id: e.id,
180
- input: o
181
- } })), c(!1);
183
+ input: s
184
+ } })), o(!1);
182
185
  } catch (e) {
183
186
  m(e instanceof Error ? e.message : "The request could not be saved.");
184
187
  }
185
- }, P = "text-sm font-medium";
186
- return /* @__PURE__ */ _(b, {
187
- open: s,
188
+ }, N = "text-sm font-medium";
189
+ return /* @__PURE__ */ v(x, {
190
+ open: r,
188
191
  onOpenChange: (e) => {
189
- c(e), e ? f(i) : m(null);
192
+ o(e), e ? d(t) : m(null);
190
193
  },
191
- children: [/* @__PURE__ */ g(E, {
194
+ children: [/* @__PURE__ */ _(D, {
192
195
  asChild: !0,
193
- children: /* @__PURE__ */ _(v, {
196
+ children: /* @__PURE__ */ v(y, {
194
197
  size: "sm",
195
198
  variant: "outline",
196
199
  "data-testid": "feature-edit",
197
- children: [/* @__PURE__ */ g(d, { className: "mr-1 h-4 w-4" }), "Edit"]
200
+ children: [/* @__PURE__ */ _(f, { className: "mr-1 h-4 w-4" }), "Edit"]
198
201
  })
199
- }), /* @__PURE__ */ _(x, {
202
+ }), /* @__PURE__ */ v(S, {
200
203
  className: "max-w-lg",
201
204
  children: [
202
- /* @__PURE__ */ _(w, { children: [/* @__PURE__ */ g(T, { children: "Edit feature request" }), /* @__PURE__ */ g(S, { children: "Link the request to the work item that delivers it so releases can show who is waiting on it." })] }),
203
- /* @__PURE__ */ _("div", {
205
+ /* @__PURE__ */ v(T, { children: [/* @__PURE__ */ _(E, { children: "Edit feature request" }), /* @__PURE__ */ _(C, { children: "Link the request to the work item that delivers it so releases can show who is waiting on it." })] }),
206
+ /* @__PURE__ */ v("div", {
204
207
  className: "space-y-3",
205
208
  children: [
206
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
209
+ /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
207
210
  htmlFor: "feature-edit-title",
208
- className: P,
211
+ className: N,
209
212
  children: "Title"
210
- }), /* @__PURE__ */ g(y, {
213
+ }), /* @__PURE__ */ _(b, {
211
214
  id: "feature-edit-title",
212
215
  className: "mt-1",
213
- value: u.title,
214
- onChange: (e) => f({
215
- ...u,
216
+ value: l.title,
217
+ onChange: (e) => d({
218
+ ...l,
216
219
  title: e.target.value
217
220
  })
218
221
  })] }),
219
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
222
+ /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
220
223
  htmlFor: "feature-edit-description",
221
- className: P,
224
+ className: N,
222
225
  children: "Description"
223
- }), /* @__PURE__ */ g("textarea", {
226
+ }), /* @__PURE__ */ _("textarea", {
224
227
  id: "feature-edit-description",
225
228
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]",
226
- value: u.description,
227
- onChange: (e) => f({
228
- ...u,
229
+ value: l.description,
230
+ onChange: (e) => d({
231
+ ...l,
229
232
  description: e.target.value
230
233
  })
231
234
  })] }),
232
- /* @__PURE__ */ _("div", {
235
+ /* @__PURE__ */ v("div", {
233
236
  className: "grid grid-cols-2 gap-3",
234
- children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
237
+ children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
235
238
  htmlFor: "feature-edit-value",
236
- className: P,
239
+ className: N,
237
240
  children: "Business value (0–100)"
238
- }), /* @__PURE__ */ g(y, {
241
+ }), /* @__PURE__ */ _(b, {
239
242
  id: "feature-edit-value",
240
243
  type: "number",
241
244
  min: 0,
242
245
  max: 100,
243
246
  className: "mt-1",
244
- value: u.businessValue,
247
+ value: l.businessValue,
245
248
  "aria-invalid": k.invalid,
246
- onChange: (e) => f({
247
- ...u,
249
+ onChange: (e) => d({
250
+ ...l,
248
251
  businessValue: e.target.value
249
252
  })
250
- })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
253
+ })] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
251
254
  htmlFor: "feature-edit-effort",
252
- className: P,
255
+ className: N,
253
256
  children: "Effort"
254
- }), /* @__PURE__ */ g(y, {
257
+ }), /* @__PURE__ */ _(b, {
255
258
  id: "feature-edit-effort",
256
259
  className: "mt-1",
257
260
  placeholder: "e.g. M",
258
- value: u.effortEstimate,
259
- onChange: (e) => f({
260
- ...u,
261
+ value: l.effortEstimate,
262
+ onChange: (e) => d({
263
+ ...l,
261
264
  effortEstimate: e.target.value
262
265
  })
263
266
  })] })]
264
267
  }),
265
- k.invalid && /* @__PURE__ */ g(a, { message: "Business value is a whole number from 0 to 100." }),
266
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
268
+ k.invalid && /* @__PURE__ */ _(s, { message: "Business value is a whole number from 0 to 100." }),
269
+ /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
267
270
  htmlFor: "feature-edit-project",
268
- className: P,
271
+ className: N,
269
272
  children: "Project"
270
- }), /* @__PURE__ */ g("div", {
273
+ }), /* @__PURE__ */ _("div", {
271
274
  className: "mt-1",
272
- children: /* @__PURE__ */ g(n, {
275
+ children: /* @__PURE__ */ _(i, {
273
276
  id: "feature-edit-project",
274
- value: u.projectId,
277
+ value: l.projectId,
275
278
  noneLabel: "No project",
276
- onChange: (e) => f({
277
- ...u,
279
+ onChange: (e) => d({
280
+ ...l,
278
281
  projectId: e,
279
282
  workItemId: ""
280
283
  })
281
284
  })
282
285
  })] }),
283
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
286
+ /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
284
287
  htmlFor: "feature-edit-workitem",
285
- className: P,
288
+ className: N,
286
289
  children: "Delivered by work item"
287
- }), /* @__PURE__ */ g("div", {
290
+ }), /* @__PURE__ */ _("div", {
288
291
  className: "mt-1",
289
- children: /* @__PURE__ */ g(r, {
292
+ children: /* @__PURE__ */ _(a, {
290
293
  id: "feature-edit-workitem",
291
- projectId: u.projectId || void 0,
292
- value: u.workItemId,
293
- onChange: (e) => f({
294
- ...u,
294
+ projectId: l.projectId || void 0,
295
+ value: l.workItemId,
296
+ onChange: (e) => d({
297
+ ...l,
295
298
  workItemId: e
296
299
  })
297
300
  })
298
301
  })] }),
299
- p && /* @__PURE__ */ g(a, { message: p })
302
+ p && /* @__PURE__ */ _(s, { message: p })
300
303
  ]
301
304
  }),
302
- /* @__PURE__ */ _(C, { children: [/* @__PURE__ */ g(v, {
305
+ /* @__PURE__ */ v(w, { children: [/* @__PURE__ */ _(y, {
303
306
  variant: "outline",
304
- onClick: () => c(!1),
307
+ onClick: () => o(!1),
305
308
  children: "Cancel"
306
- }), /* @__PURE__ */ g(v, {
307
- onClick: () => void M(),
309
+ }), /* @__PURE__ */ _(y, {
310
+ onClick: () => void j(),
308
311
  disabled: !A,
309
312
  children: O.loading ? "Saving…" : "Save"
310
313
  })] })
@@ -313,6 +316,6 @@ function I({ feature: e }) {
313
316
  });
314
317
  }
315
318
  //#endregion
316
- export { F as FeatureHeaderActions, P as FeatureVoteButton };
319
+ export { I as FeatureHeaderActions, F as FeatureVoteButton };
317
320
 
318
321
  //# sourceMappingURL=FeatureControls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FeatureControls.js","names":[],"sources":["../../../src/planmagnet/controls/FeatureControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { BarChart3, Pencil, ThumbsUp, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { parseIntField } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { ProjectSelect, WorkItemSearchSelect } from './Pickers';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Feature request controls (BOFF-7951/7963). Feature requests were read-only: no vote, no status change after\n * creation, no edit or delete — so every UI-created request stayed at 0 votes and its creation status, and the\n * Declined/Duplicate tabs could only ever be empty.\n */\n\nexport const FEATURE_STATUSES = [\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DECLINED',\n 'DUPLICATE',\n] as const;\n\n/** Every query that shows a feature request, its votes or feature counts. */\nconst FEATURE_QUERIES = [\n 'GetFeatureRequest',\n 'ListFeatureRequests',\n 'ListFeatureRequestsForProduct',\n 'ListFeatureRequestsForProducts',\n 'ListAllFeaturesAnalytics',\n 'GetFeatureRequestAnalytics',\n 'ReleaseCustomerRequests',\n];\n\nconst VOTE = gql`\n mutation FeatureControlsVote($id: ID!) {\n planmagnetVoteOnFeature(featureRequestId: $id) {\n id\n }\n }\n`;\nconst UNVOTE = gql`\n mutation FeatureControlsUnvote($id: ID!) {\n planmagnetRemoveFeatureVote(featureRequestId: $id)\n }\n`;\nconst UPDATE = gql`\n mutation FeatureControlsUpdate($id: ID!, $input: PlanmagnetUpdateFeatureRequestInput!) {\n planmagnetUpdateFeatureRequest(id: $id, input: $input) {\n id\n title\n description\n status\n businessValue\n effortEstimate\n projectId\n workItemId\n }\n }\n`;\nconst DELETE = gql`\n mutation FeatureControlsDelete($id: ID!) {\n planmagnetDeleteFeatureRequest(id: $id)\n }\n`;\n\nconst options = { refetchQueries: FEATURE_QUERIES, awaitRefetchQueries: true };\n\n/** Vote for a request, or take your vote back. */\nexport function FeatureVoteButton({ featureId, voted }: { featureId: string; voted: boolean }) {\n const [vote, voteState] = useMutation(VOTE, options);\n const [unvote, unvoteState] = useMutation(UNVOTE, options);\n const [error, setError] = useState<string | null>(null);\n const busy = voteState.loading || unvoteState.loading;\n const toggle = async () => {\n setError(null);\n try {\n await throwOnError((voted ? unvote : vote)({ variables: { id: featureId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Your vote could not be saved.');\n }\n };\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <Button\n size=\"sm\"\n variant={voted ? 'default' : 'outline'}\n data-testid=\"feature-vote\"\n aria-pressed={voted}\n disabled={busy}\n onClick={() => void toggle()}\n >\n <ThumbsUp className=\"mr-1 h-4 w-4\" />\n {voted ? 'Voted' : 'Vote'}\n </Button>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nexport interface FeatureValues {\n id: string;\n title: string;\n description: string | null;\n status: string;\n businessValue: number | null;\n effortEstimate: string | null;\n projectId: string | null;\n workItemId: string | null;\n}\n\n/** Status select, Edit and Delete for the feature detail header. */\nexport function FeatureHeaderActions({ feature }: { feature: FeatureValues }) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, options);\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: FEATURE_QUERIES.filter((q) => q !== 'GetFeatureRequest'),\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (FEATURE_STATUSES as readonly string[]).includes(feature.status)\n ? FEATURE_STATUSES\n : [feature.status, ...FEATURE_STATUSES];\n\n const onStatus = async (status: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: feature.id, input: { status } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: feature.id } }));\n navigate('/products/features-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The request could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Status\"\n data-testid=\"feature-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={feature.status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== feature.status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <FeatureEditDialog feature={feature} />\n {/* BOFF-7983: the analytics page existed with no way in. */}\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"feature-analytics\"\n onClick={() => navigate(detailPath('/products/feature-analytics-page', feature.id))}\n >\n <BarChart3 className=\"mr-1 h-4 w-4\" />\n Analytics\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${feature.title}\"?`}\n description=\"The request and its votes are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete request\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Edit a request's fields, including the project and the work item it rides on (the release readiness card needs\n * that link, and no UI could set it — BOFF-7966). Only changed fields are sent; an emptied field is cleared.\n */\nfunction FeatureEditDialog({ feature }: { feature: FeatureValues }) {\n const initial = {\n title: feature.title,\n description: feature.description ?? '',\n businessValue: feature.businessValue == null ? '' : String(feature.businessValue),\n effortEstimate: feature.effortEstimate ?? '',\n projectId: feature.projectId ?? '',\n workItemId: feature.workItemId ?? '',\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const [update, state] = useMutation(UPDATE, options);\n const businessValue = parseIntField(form.businessValue, { min: 0, max: 100 });\n const canSave = form.title.trim() !== '' && !businessValue.invalid && !state.loading;\n\n const save = async () => {\n setError(null);\n const text = (v: string) => (v.trim() ? v.trim() : null);\n const next = {\n title: form.title.trim(),\n description: text(form.description),\n businessValue: businessValue.value ?? null,\n effortEstimate: text(form.effortEstimate),\n projectId: form.projectId || null,\n workItemId: form.workItemId || null,\n };\n const before = {\n title: initial.title,\n description: text(initial.description),\n businessValue: initial.businessValue === '' ? null : Number(initial.businessValue),\n effortEstimate: text(initial.effortEstimate),\n projectId: initial.projectId || null,\n workItemId: initial.workItemId || null,\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(([k, v]) => v !== before[k as keyof typeof before])\n );\n if (Object.keys(input).length === 0) {\n setOpen(false);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: feature.id, input } }));\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The request could not be saved.');\n }\n };\n\n const label = 'text-sm font-medium';\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"feature-edit\">\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit feature request</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link the request to the work item that delivers it so releases can show who is waiting\n on it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"feature-edit-title\" className={label}>\n Title\n </label>\n <Input\n id=\"feature-edit-title\"\n className=\"mt-1\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"feature-edit-description\" className={label}>\n Description\n </label>\n <textarea\n id=\"feature-edit-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"feature-edit-value\" className={label}>\n Business value (0–100)\n </label>\n <Input\n id=\"feature-edit-value\"\n type=\"number\"\n min={0}\n max={100}\n className=\"mt-1\"\n value={form.businessValue}\n aria-invalid={businessValue.invalid}\n onChange={(e) => setForm({ ...form, businessValue: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"feature-edit-effort\" className={label}>\n Effort\n </label>\n <Input\n id=\"feature-edit-effort\"\n className=\"mt-1\"\n placeholder=\"e.g. M\"\n value={form.effortEstimate}\n onChange={(e) => setForm({ ...form, effortEstimate: e.target.value })}\n />\n </div>\n </div>\n {businessValue.invalid && (\n <ErrorLine message=\"Business value is a whole number from 0 to 100.\" />\n )}\n <div>\n <label htmlFor=\"feature-edit-project\" className={label}>\n Project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"feature-edit-project\"\n value={form.projectId}\n noneLabel=\"No project\"\n onChange={(projectId) => setForm({ ...form, projectId, workItemId: '' })}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"feature-edit-workitem\" className={label}>\n Delivered by work item\n </label>\n <div className=\"mt-1\">\n <WorkItemSearchSelect\n id=\"feature-edit-workitem\"\n projectId={form.projectId || undefined}\n value={form.workItemId}\n onChange={(workItemId) => setForm({ ...form, workItemId })}\n />\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSave}>\n {state.loading ? 'Saving…' : 'Save'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FeatureControls.js","names":[],"sources":["../../../src/planmagnet/controls/FeatureControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { BarChart3, Pencil, ThumbsUp, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { parseIntField } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { ProjectSelect, WorkItemSearchSelect } from './Pickers';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Feature request controls (BOFF-7951/7963). Feature requests were read-only: no vote, no status change after\n * creation, no edit or delete — so every UI-created request stayed at 0 votes and its creation status, and the\n * Declined/Duplicate tabs could only ever be empty.\n */\n\nexport const FEATURE_STATUSES = [\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DECLINED',\n 'DUPLICATE',\n] as const;\n\n/** Every query that shows a feature request, its votes or feature counts. */\nconst FEATURE_QUERIES = [\n 'GetFeatureRequest',\n 'ListFeatureRequests',\n 'ListFeatureRequestsForProduct',\n 'ListFeatureRequestsForProducts',\n 'ListAllFeaturesAnalytics',\n 'GetFeatureRequestAnalytics',\n 'ReleaseCustomerRequests',\n];\n\nconst VOTE = gql`\n mutation FeatureControlsVote($id: ID!) {\n planmagnetVoteOnFeature(featureRequestId: $id) {\n id\n }\n }\n`;\nconst UNVOTE = gql`\n mutation FeatureControlsUnvote($id: ID!) {\n planmagnetRemoveFeatureVote(featureRequestId: $id)\n }\n`;\nconst UPDATE = gql`\n mutation FeatureControlsUpdate($id: ID!, $input: PlanmagnetUpdateFeatureRequestInput!) {\n planmagnetUpdateFeatureRequest(id: $id, input: $input) {\n id\n title\n description\n status\n businessValue\n effortEstimate\n projectId\n workItemId\n }\n }\n`;\nconst DELETE = gql`\n mutation FeatureControlsDelete($id: ID!) {\n planmagnetDeleteFeatureRequest(id: $id)\n }\n`;\n\nconst options = { refetchQueries: FEATURE_QUERIES, awaitRefetchQueries: true };\n\n/** Vote for a request, or take your vote back. */\nexport function FeatureVoteButton({ featureId, voted }: { featureId: string; voted: boolean }) {\n const [vote, voteState] = useMutation(VOTE, options);\n const [unvote, unvoteState] = useMutation(UNVOTE, options);\n const [error, setError] = useState<string | null>(null);\n const busy = voteState.loading || unvoteState.loading;\n const toggle = async () => {\n setError(null);\n try {\n await throwOnError((voted ? unvote : vote)({ variables: { id: featureId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Your vote could not be saved.');\n }\n };\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <Button\n size=\"sm\"\n variant={voted ? 'default' : 'outline'}\n data-testid=\"feature-vote\"\n aria-pressed={voted}\n disabled={busy}\n onClick={() => void toggle()}\n >\n <ThumbsUp className=\"mr-1 h-4 w-4\" />\n {voted ? 'Voted' : 'Vote'}\n </Button>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nexport interface FeatureValues {\n id: string;\n title: string;\n description: string | null;\n status: string;\n businessValue: number | null;\n effortEstimate: string | null;\n projectId: string | null;\n workItemId: string | null;\n}\n\n/** Status select, Edit and Delete for the feature detail header. */\nexport function FeatureHeaderActions({ feature }: { feature: FeatureValues }) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, options);\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: FEATURE_QUERIES.filter((q) => q !== 'GetFeatureRequest'),\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (FEATURE_STATUSES as readonly string[]).includes(feature.status)\n ? FEATURE_STATUSES\n : [feature.status, ...FEATURE_STATUSES];\n\n const onStatus = async (status: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: feature.id, input: { status } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: feature.id } }));\n navigate('/products/features-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The request could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Status\"\n data-testid=\"feature-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={feature.status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== feature.status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <FeatureEditDialog feature={feature} />\n {/* BOFF-7983: the analytics page existed with no way in. */}\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"feature-analytics\"\n onClick={() => navigate(detailPath('/products/feature-analytics-page', feature.id))}\n >\n <BarChart3 className=\"mr-1 h-4 w-4\" />\n Analytics\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${feature.title}\"?`}\n description=\"The request and its votes are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete request\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Edit a request's fields, including the project and the work item it rides on (the release readiness card needs\n * that link, and no UI could set it — BOFF-7966). Only changed fields are sent; an emptied field is cleared.\n */\nfunction FeatureEditDialog({ feature }: { feature: FeatureValues }) {\n const initial = {\n title: feature.title,\n description: feature.description ?? '',\n businessValue: feature.businessValue == null ? '' : String(feature.businessValue),\n effortEstimate: feature.effortEstimate ?? '',\n projectId: feature.projectId ?? '',\n workItemId: feature.workItemId ?? '',\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const [update, state] = useMutation(UPDATE, options);\n const businessValue = parseIntField(form.businessValue, { min: 0, max: 100 });\n const canSave = form.title.trim() !== '' && !businessValue.invalid && !state.loading;\n\n const save = async () => {\n setError(null);\n const text = (v: string) => (v.trim() ? v.trim() : null);\n const next = {\n title: form.title.trim(),\n description: text(form.description),\n businessValue: businessValue.value ?? null,\n effortEstimate: text(form.effortEstimate),\n projectId: form.projectId || null,\n workItemId: form.workItemId || null,\n };\n const before = {\n title: initial.title,\n description: text(initial.description),\n businessValue: initial.businessValue === '' ? null : Number(initial.businessValue),\n effortEstimate: text(initial.effortEstimate),\n projectId: initial.projectId || null,\n workItemId: initial.workItemId || null,\n };\n const input = Object.fromEntries(\n Object.entries(next).filter(([k, v]) => v !== before[k as keyof typeof before])\n );\n if (Object.keys(input).length === 0) {\n setOpen(false);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: feature.id, input } }));\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The request could not be saved.');\n }\n };\n\n const label = 'text-sm font-medium';\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"feature-edit\">\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit feature request</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link the request to the work item that delivers it so releases can show who is waiting\n on it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"feature-edit-title\" className={label}>\n Title\n </label>\n <Input\n id=\"feature-edit-title\"\n className=\"mt-1\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"feature-edit-description\" className={label}>\n Description\n </label>\n <textarea\n id=\"feature-edit-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"feature-edit-value\" className={label}>\n Business value (0–100)\n </label>\n <Input\n id=\"feature-edit-value\"\n type=\"number\"\n min={0}\n max={100}\n className=\"mt-1\"\n value={form.businessValue}\n aria-invalid={businessValue.invalid}\n onChange={(e) => setForm({ ...form, businessValue: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"feature-edit-effort\" className={label}>\n Effort\n </label>\n <Input\n id=\"feature-edit-effort\"\n className=\"mt-1\"\n placeholder=\"e.g. M\"\n value={form.effortEstimate}\n onChange={(e) => setForm({ ...form, effortEstimate: e.target.value })}\n />\n </div>\n </div>\n {businessValue.invalid && (\n <ErrorLine message=\"Business value is a whole number from 0 to 100.\" />\n )}\n <div>\n <label htmlFor=\"feature-edit-project\" className={label}>\n Project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"feature-edit-project\"\n value={form.projectId}\n noneLabel=\"No project\"\n onChange={(projectId) => setForm({ ...form, projectId, workItemId: '' })}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"feature-edit-workitem\" className={label}>\n Delivered by work item\n </label>\n <div className=\"mt-1\">\n <WorkItemSearchSelect\n id=\"feature-edit-workitem\"\n projectId={form.projectId || undefined}\n value={form.workItemId}\n onChange={(workItemId) => setForm({ ...form, workItemId })}\n />\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSave}>\n {state.loading ? 'Saving…' : 'Save'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare const DECLINE_REASON_MAX = 2000;
2
+ export interface TriageFeature {
3
+ id: string;
4
+ title: string;
5
+ status: string;
6
+ projectId?: string | null;
7
+ snoozedUntil?: string | null;
8
+ }
9
+ /** True while the request waits for a decision (accept, decline and merge apply). */
10
+ export declare const isOpenRequest: (status: string) => boolean;
11
+ /** A snooze that has not passed yet. */
12
+ export declare const isSnoozed: (snoozedUntil: string | null | undefined, now: number) => boolean;
13
+ /**
14
+ * The four triage actions for an undecided request. Snooze applies only while it waits for triage (SUBMITTED);
15
+ * a snoozed request offers "Back to triage" instead.
16
+ */
17
+ export declare function FeatureTriageActions({ feature, now }: {
18
+ feature: TriageFeature;
19
+ now: number;
20
+ }): import("react/jsx-runtime").JSX.Element | null;
21
+ //# sourceMappingURL=FeatureTriage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeatureTriage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/FeatureTriage.tsx"],"names":[],"mappings":"AAyFA,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAGvC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,qFAAqF;AACrF,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,YAA0B,CAAC;AACvE,wCAAwC;AACxC,eAAO,MAAM,SAAS,GAAI,cAAc,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,MAAM,YACpB,CAAC;AAuZ3D;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,kDAoB7F"}