@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.
- package/dist/generated/global-types.d.ts +9 -1
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-operations.d.ts +0 -12
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.d.ts +2 -0
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +492 -225
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +142 -139
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
- package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeatureTriage.js +398 -0
- package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.js +76 -73
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +47 -44
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +47 -44
- package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +68 -65
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
- package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
- package/dist/planmagnet/hooks/useCounts.js +209 -0
- package/dist/planmagnet/hooks/useCounts.js.map +1 -0
- package/dist/planmagnet/import/exportColumns.d.ts +52 -0
- package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
- package/dist/planmagnet/import/exportColumns.js +81 -0
- package/dist/planmagnet/import/exportColumns.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +152 -139
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +75 -74
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
- package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +32 -30
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
- package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
- package/dist/planmagnet/utils/mutationResult.js +2 -1
- package/dist/planmagnet/utils/mutationResult.js.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
- package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.js +9 -8
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { useState as
|
|
9
|
-
import { BarChart3 as
|
|
10
|
-
import { gql as
|
|
11
|
-
import { useMutation as
|
|
12
|
-
import { jsx as
|
|
13
|
-
import { Button as
|
|
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
|
|
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
|
-
],
|
|
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
|
-
],
|
|
31
|
+
], A = h`
|
|
32
32
|
mutation FeatureControlsVote($id: ID!) {
|
|
33
33
|
planmagnetVoteOnFeature(featureRequestId: $id) {
|
|
34
34
|
id
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
`,
|
|
37
|
+
`, j = h`
|
|
38
38
|
mutation FeatureControlsUnvote($id: ID!) {
|
|
39
39
|
planmagnetRemoveFeatureVote(featureRequestId: $id)
|
|
40
40
|
}
|
|
41
|
-
`,
|
|
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
|
-
`,
|
|
54
|
+
`, N = h`
|
|
55
55
|
mutation FeatureControlsDelete($id: ID!) {
|
|
56
56
|
planmagnetDeleteFeatureRequest(id: $id)
|
|
57
57
|
}
|
|
58
|
-
`,
|
|
59
|
-
refetchQueries:
|
|
58
|
+
`, P = {
|
|
59
|
+
refetchQueries: k,
|
|
60
60
|
awaitRefetchQueries: !0
|
|
61
61
|
};
|
|
62
|
-
function
|
|
63
|
-
let [r, i] =
|
|
64
|
-
|
|
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
|
|
66
|
+
await n((t ? a : r)({ variables: { id: e } }));
|
|
67
67
|
} catch (e) {
|
|
68
|
-
|
|
68
|
+
l(e instanceof Error ? e.message : "Your vote could not be saved.");
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
-
return /* @__PURE__ */
|
|
71
|
+
return /* @__PURE__ */ v("div", {
|
|
72
72
|
className: "flex flex-col items-end gap-1",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ v(y, {
|
|
74
74
|
size: "sm",
|
|
75
|
-
variant:
|
|
75
|
+
variant: t ? "default" : "outline",
|
|
76
76
|
"data-testid": "feature-vote",
|
|
77
|
-
"aria-pressed":
|
|
77
|
+
"aria-pressed": t,
|
|
78
78
|
disabled: d,
|
|
79
|
-
onClick: () => void
|
|
80
|
-
children: [/* @__PURE__ */
|
|
81
|
-
}), c && /* @__PURE__ */
|
|
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
|
|
85
|
-
let
|
|
86
|
-
|
|
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
|
|
89
|
-
id:
|
|
91
|
+
await n(c({ variables: {
|
|
92
|
+
id: i.id,
|
|
90
93
|
input: { status: e }
|
|
91
94
|
} }));
|
|
92
95
|
} catch (e) {
|
|
93
|
-
|
|
96
|
+
x(e instanceof Error ? e.message : "The status could not be changed.");
|
|
94
97
|
}
|
|
95
|
-
},
|
|
96
|
-
|
|
98
|
+
}, T = async () => {
|
|
99
|
+
x(null);
|
|
97
100
|
try {
|
|
98
|
-
await
|
|
101
|
+
await n(p({ variables: { id: i.id } })), a("/products/features-page");
|
|
99
102
|
} catch (e) {
|
|
100
|
-
|
|
103
|
+
x(e instanceof Error ? e.message : "The request could not be deleted.");
|
|
101
104
|
}
|
|
102
105
|
};
|
|
103
|
-
return /* @__PURE__ */
|
|
106
|
+
return /* @__PURE__ */ v("div", {
|
|
104
107
|
className: "flex flex-col items-end gap-2",
|
|
105
|
-
children: [/* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ v("div", {
|
|
106
109
|
className: "flex flex-wrap items-center justify-end gap-2",
|
|
107
110
|
children: [
|
|
108
|
-
/* @__PURE__ */
|
|
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:
|
|
113
|
-
disabled:
|
|
115
|
+
value: i.status,
|
|
116
|
+
disabled: S,
|
|
114
117
|
onChange: (e) => {
|
|
115
|
-
e.target.value !==
|
|
118
|
+
e.target.value !== i.status && w(e.target.value);
|
|
116
119
|
},
|
|
117
|
-
children:
|
|
120
|
+
children: C.map((e) => /* @__PURE__ */ _("option", {
|
|
118
121
|
value: e,
|
|
119
|
-
children:
|
|
122
|
+
children: l(e)
|
|
120
123
|
}, e))
|
|
121
124
|
}),
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
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(
|
|
128
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
133
|
+
/* @__PURE__ */ _(o, {
|
|
131
134
|
label: "Delete",
|
|
132
135
|
variant: "outline",
|
|
133
|
-
icon: /* @__PURE__ */
|
|
134
|
-
title: `Delete "${
|
|
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:
|
|
139
|
-
onConfirm: () => void
|
|
141
|
+
disabled: S,
|
|
142
|
+
onConfirm: () => void T()
|
|
140
143
|
})
|
|
141
144
|
]
|
|
142
|
-
}),
|
|
145
|
+
}), b && /* @__PURE__ */ _(s, { message: b })]
|
|
143
146
|
});
|
|
144
147
|
}
|
|
145
|
-
function
|
|
146
|
-
let
|
|
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
|
-
}, [
|
|
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 =
|
|
159
|
+
}), A = l.title.trim() !== "" && !k.invalid && !O.loading, j = async () => {
|
|
157
160
|
m(null);
|
|
158
|
-
let
|
|
159
|
-
title:
|
|
160
|
-
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:
|
|
163
|
-
projectId:
|
|
164
|
-
workItemId:
|
|
165
|
+
effortEstimate: r(l.effortEstimate),
|
|
166
|
+
projectId: l.projectId || null,
|
|
167
|
+
workItemId: l.workItemId || null
|
|
165
168
|
}, a = {
|
|
166
|
-
title:
|
|
167
|
-
description:
|
|
168
|
-
businessValue:
|
|
169
|
-
effortEstimate:
|
|
170
|
-
projectId:
|
|
171
|
-
workItemId:
|
|
172
|
-
},
|
|
173
|
-
if (Object.keys(
|
|
174
|
-
|
|
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
|
|
181
|
+
await n(h({ variables: {
|
|
179
182
|
id: e.id,
|
|
180
|
-
input:
|
|
181
|
-
} })),
|
|
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
|
-
},
|
|
186
|
-
return /* @__PURE__ */
|
|
187
|
-
open:
|
|
188
|
+
}, N = "text-sm font-medium";
|
|
189
|
+
return /* @__PURE__ */ v(x, {
|
|
190
|
+
open: r,
|
|
188
191
|
onOpenChange: (e) => {
|
|
189
|
-
|
|
192
|
+
o(e), e ? d(t) : m(null);
|
|
190
193
|
},
|
|
191
|
-
children: [/* @__PURE__ */
|
|
194
|
+
children: [/* @__PURE__ */ _(D, {
|
|
192
195
|
asChild: !0,
|
|
193
|
-
children: /* @__PURE__ */
|
|
196
|
+
children: /* @__PURE__ */ v(y, {
|
|
194
197
|
size: "sm",
|
|
195
198
|
variant: "outline",
|
|
196
199
|
"data-testid": "feature-edit",
|
|
197
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ _(f, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
198
201
|
})
|
|
199
|
-
}), /* @__PURE__ */
|
|
202
|
+
}), /* @__PURE__ */ v(S, {
|
|
200
203
|
className: "max-w-lg",
|
|
201
204
|
children: [
|
|
202
|
-
/* @__PURE__ */
|
|
203
|
-
/* @__PURE__ */
|
|
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__ */
|
|
209
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
207
210
|
htmlFor: "feature-edit-title",
|
|
208
|
-
className:
|
|
211
|
+
className: N,
|
|
209
212
|
children: "Title"
|
|
210
|
-
}), /* @__PURE__ */
|
|
213
|
+
}), /* @__PURE__ */ _(b, {
|
|
211
214
|
id: "feature-edit-title",
|
|
212
215
|
className: "mt-1",
|
|
213
|
-
value:
|
|
214
|
-
onChange: (e) =>
|
|
215
|
-
...
|
|
216
|
+
value: l.title,
|
|
217
|
+
onChange: (e) => d({
|
|
218
|
+
...l,
|
|
216
219
|
title: e.target.value
|
|
217
220
|
})
|
|
218
221
|
})] }),
|
|
219
|
-
/* @__PURE__ */
|
|
222
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
220
223
|
htmlFor: "feature-edit-description",
|
|
221
|
-
className:
|
|
224
|
+
className: N,
|
|
222
225
|
children: "Description"
|
|
223
|
-
}), /* @__PURE__ */
|
|
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:
|
|
227
|
-
onChange: (e) =>
|
|
228
|
-
...
|
|
229
|
+
value: l.description,
|
|
230
|
+
onChange: (e) => d({
|
|
231
|
+
...l,
|
|
229
232
|
description: e.target.value
|
|
230
233
|
})
|
|
231
234
|
})] }),
|
|
232
|
-
/* @__PURE__ */
|
|
235
|
+
/* @__PURE__ */ v("div", {
|
|
233
236
|
className: "grid grid-cols-2 gap-3",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
237
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
235
238
|
htmlFor: "feature-edit-value",
|
|
236
|
-
className:
|
|
239
|
+
className: N,
|
|
237
240
|
children: "Business value (0–100)"
|
|
238
|
-
}), /* @__PURE__ */
|
|
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:
|
|
247
|
+
value: l.businessValue,
|
|
245
248
|
"aria-invalid": k.invalid,
|
|
246
|
-
onChange: (e) =>
|
|
247
|
-
...
|
|
249
|
+
onChange: (e) => d({
|
|
250
|
+
...l,
|
|
248
251
|
businessValue: e.target.value
|
|
249
252
|
})
|
|
250
|
-
})] }), /* @__PURE__ */
|
|
253
|
+
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
251
254
|
htmlFor: "feature-edit-effort",
|
|
252
|
-
className:
|
|
255
|
+
className: N,
|
|
253
256
|
children: "Effort"
|
|
254
|
-
}), /* @__PURE__ */
|
|
257
|
+
}), /* @__PURE__ */ _(b, {
|
|
255
258
|
id: "feature-edit-effort",
|
|
256
259
|
className: "mt-1",
|
|
257
260
|
placeholder: "e.g. M",
|
|
258
|
-
value:
|
|
259
|
-
onChange: (e) =>
|
|
260
|
-
...
|
|
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__ */
|
|
266
|
-
/* @__PURE__ */
|
|
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:
|
|
271
|
+
className: N,
|
|
269
272
|
children: "Project"
|
|
270
|
-
}), /* @__PURE__ */
|
|
273
|
+
}), /* @__PURE__ */ _("div", {
|
|
271
274
|
className: "mt-1",
|
|
272
|
-
children: /* @__PURE__ */
|
|
275
|
+
children: /* @__PURE__ */ _(i, {
|
|
273
276
|
id: "feature-edit-project",
|
|
274
|
-
value:
|
|
277
|
+
value: l.projectId,
|
|
275
278
|
noneLabel: "No project",
|
|
276
|
-
onChange: (e) =>
|
|
277
|
-
...
|
|
279
|
+
onChange: (e) => d({
|
|
280
|
+
...l,
|
|
278
281
|
projectId: e,
|
|
279
282
|
workItemId: ""
|
|
280
283
|
})
|
|
281
284
|
})
|
|
282
285
|
})] }),
|
|
283
|
-
/* @__PURE__ */
|
|
286
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
284
287
|
htmlFor: "feature-edit-workitem",
|
|
285
|
-
className:
|
|
288
|
+
className: N,
|
|
286
289
|
children: "Delivered by work item"
|
|
287
|
-
}), /* @__PURE__ */
|
|
290
|
+
}), /* @__PURE__ */ _("div", {
|
|
288
291
|
className: "mt-1",
|
|
289
|
-
children: /* @__PURE__ */
|
|
292
|
+
children: /* @__PURE__ */ _(a, {
|
|
290
293
|
id: "feature-edit-workitem",
|
|
291
|
-
projectId:
|
|
292
|
-
value:
|
|
293
|
-
onChange: (e) =>
|
|
294
|
-
...
|
|
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__ */
|
|
302
|
+
p && /* @__PURE__ */ _(s, { message: p })
|
|
300
303
|
]
|
|
301
304
|
}),
|
|
302
|
-
/* @__PURE__ */
|
|
305
|
+
/* @__PURE__ */ v(w, { children: [/* @__PURE__ */ _(y, {
|
|
303
306
|
variant: "outline",
|
|
304
|
-
onClick: () =>
|
|
307
|
+
onClick: () => o(!1),
|
|
305
308
|
children: "Cancel"
|
|
306
|
-
}), /* @__PURE__ */
|
|
307
|
-
onClick: () => void
|
|
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 {
|
|
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"}
|