@burdenoff/microfe-planmagnet 2026.924.5 → 2026.925.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +191 -172
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +96 -104
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +59 -51
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +115 -105
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.d.ts +39 -0
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -0
- package/dist/planmagnet/sprints/SprintControls.js +522 -0
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { useDeferredValue as t, useState as n } from "react";
|
|
3
|
+
import { CheckCircle2 as r, Play as i, Plus as a, X as o, XCircle as s } from "lucide-react";
|
|
4
|
+
import { gql as c } from "@apollo/client";
|
|
5
|
+
import { useMutation as l, useQuery as u } from "@apollo/client/react";
|
|
6
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
|
+
import { AlertDialog as p, AlertDialogAction as m, AlertDialogCancel as h, AlertDialogContent as g, AlertDialogDescription as _, AlertDialogFooter as v, AlertDialogHeader as y, AlertDialogTitle as b, AlertDialogTrigger as x, Badge as S, Button as C, Checkbox as w, Input as T, ResponsiveDialog as E, ResponsiveDialogContent as D, ResponsiveDialogDescription as O, ResponsiveDialogFooter as k, ResponsiveDialogHeader as A, ResponsiveDialogTitle as j, ResponsiveDialogTrigger as M } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
//#region src/planmagnet/sprints/SprintControls.tsx
|
|
9
|
+
var N = {
|
|
10
|
+
start: ["PLANNING"],
|
|
11
|
+
complete: ["ACTIVE", "REVIEW"],
|
|
12
|
+
cancel: [
|
|
13
|
+
"PLANNING",
|
|
14
|
+
"ACTIVE",
|
|
15
|
+
"REVIEW"
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
function P(e) {
|
|
19
|
+
return Object.keys(N).filter((t) => N[t].includes(e));
|
|
20
|
+
}
|
|
21
|
+
function F(e) {
|
|
22
|
+
return e === "COMPLETED" || e === "CANCELLED";
|
|
23
|
+
}
|
|
24
|
+
var I = [
|
|
25
|
+
"GetSprints",
|
|
26
|
+
"GetSprintDetail",
|
|
27
|
+
"GetSprintForEdit",
|
|
28
|
+
"GetDashboardData"
|
|
29
|
+
], L = c`
|
|
30
|
+
mutation SprintControlsStart($id: ID!) {
|
|
31
|
+
planmagnetStartSprint(id: $id) {
|
|
32
|
+
id
|
|
33
|
+
status
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`, R = c`
|
|
37
|
+
mutation SprintControlsComplete($id: ID!) {
|
|
38
|
+
planmagnetCompleteSprint(id: $id) {
|
|
39
|
+
id
|
|
40
|
+
status
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`, z = c`
|
|
44
|
+
mutation SprintControlsCancel($id: ID!) {
|
|
45
|
+
planmagnetCancelSprint(id: $id) {
|
|
46
|
+
id
|
|
47
|
+
status
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
function B({ message: e }) {
|
|
52
|
+
return /* @__PURE__ */ d("p", {
|
|
53
|
+
role: "alert",
|
|
54
|
+
className: "text-sm text-destructive",
|
|
55
|
+
children: e
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function V({ sprintId: e, sprintName: t, status: a }) {
|
|
59
|
+
let o = {
|
|
60
|
+
refetchQueries: I,
|
|
61
|
+
awaitRefetchQueries: !0
|
|
62
|
+
}, [c, u] = l(L, o), [p, m] = l(R, o), [h, g] = l(z, o), [_, v] = n(null), y = u.loading || m.loading || g.loading, b = P(a);
|
|
63
|
+
if (b.length === 0) return null;
|
|
64
|
+
let x = async (t) => {
|
|
65
|
+
v(null);
|
|
66
|
+
try {
|
|
67
|
+
await t({ variables: { id: e } });
|
|
68
|
+
} catch (e) {
|
|
69
|
+
v(e instanceof Error ? e.message : "The sprint could not be updated.");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return /* @__PURE__ */ f("div", {
|
|
73
|
+
className: "flex flex-col items-end gap-2",
|
|
74
|
+
"data-testid": "sprint-lifecycle-actions",
|
|
75
|
+
children: [/* @__PURE__ */ f("div", {
|
|
76
|
+
className: "flex gap-2",
|
|
77
|
+
children: [
|
|
78
|
+
b.includes("start") && /* @__PURE__ */ f(C, {
|
|
79
|
+
size: "sm",
|
|
80
|
+
onClick: () => x(c),
|
|
81
|
+
disabled: y,
|
|
82
|
+
children: [/* @__PURE__ */ d(i, { className: "mr-1 h-4 w-4" }), "Start sprint"]
|
|
83
|
+
}),
|
|
84
|
+
b.includes("complete") && /* @__PURE__ */ d(H, {
|
|
85
|
+
label: "Complete sprint",
|
|
86
|
+
icon: /* @__PURE__ */ d(r, { className: "mr-1 h-4 w-4" }),
|
|
87
|
+
title: `Complete ${t}?`,
|
|
88
|
+
description: "The sprint is closed and its work items can no longer be changed from it. This cannot be undone.",
|
|
89
|
+
confirmLabel: "Complete sprint",
|
|
90
|
+
disabled: y,
|
|
91
|
+
onConfirm: () => x(p)
|
|
92
|
+
}),
|
|
93
|
+
b.includes("cancel") && /* @__PURE__ */ d(H, {
|
|
94
|
+
label: "Cancel sprint",
|
|
95
|
+
variant: "outline",
|
|
96
|
+
icon: /* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }),
|
|
97
|
+
title: `Cancel ${t}?`,
|
|
98
|
+
description: "The sprint is abandoned and cannot be restarted. Its work items stay in it for the record. This cannot be undone.",
|
|
99
|
+
confirmLabel: "Cancel sprint",
|
|
100
|
+
destructive: !0,
|
|
101
|
+
disabled: y,
|
|
102
|
+
onConfirm: () => x(h)
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
}), _ && /* @__PURE__ */ d(B, { message: _ })]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function H({ label: e, icon: t, title: n, description: r, confirmLabel: i, onConfirm: a, disabled: o, destructive: s = !1, variant: c = "default" }) {
|
|
109
|
+
return /* @__PURE__ */ f(p, { children: [/* @__PURE__ */ d(x, {
|
|
110
|
+
asChild: !0,
|
|
111
|
+
children: /* @__PURE__ */ f(C, {
|
|
112
|
+
size: "sm",
|
|
113
|
+
variant: c,
|
|
114
|
+
disabled: o,
|
|
115
|
+
children: [t, e]
|
|
116
|
+
})
|
|
117
|
+
}), /* @__PURE__ */ f(g, { children: [/* @__PURE__ */ f(y, { children: [/* @__PURE__ */ d(b, { children: n }), /* @__PURE__ */ d(_, { children: r })] }), /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(h, { children: "Keep it" }), /* @__PURE__ */ d(m, {
|
|
118
|
+
onClick: a,
|
|
119
|
+
className: s ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "",
|
|
120
|
+
children: i
|
|
121
|
+
})] })] })] });
|
|
122
|
+
}
|
|
123
|
+
var U = c`
|
|
124
|
+
mutation SprintControlsAddItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {
|
|
125
|
+
planmagnetAddWorkItemsToSprint(id: $id, input: $input) {
|
|
126
|
+
id
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
`, W = c`
|
|
130
|
+
mutation SprintControlsRemoveItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {
|
|
131
|
+
planmagnetRemoveWorkItemsFromSprint(id: $id, input: $input) {
|
|
132
|
+
id
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
`, G = c`
|
|
136
|
+
query SprintControlsCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {
|
|
137
|
+
planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {
|
|
138
|
+
totalCount
|
|
139
|
+
items {
|
|
140
|
+
id
|
|
141
|
+
key
|
|
142
|
+
title
|
|
143
|
+
status
|
|
144
|
+
storyPoints
|
|
145
|
+
sprintId
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`, K = (e) => e.toLowerCase().replace(/_/g, " ");
|
|
150
|
+
function q({ sprintId: e, projectId: t, status: r, items: i }) {
|
|
151
|
+
let a = F(r), [s, c] = l(W, {
|
|
152
|
+
refetchQueries: I,
|
|
153
|
+
awaitRefetchQueries: !0
|
|
154
|
+
}), [u, p] = n(null), [m, h] = n(null), g = async (t) => {
|
|
155
|
+
p(null), h(t);
|
|
156
|
+
try {
|
|
157
|
+
await s({ variables: {
|
|
158
|
+
id: e,
|
|
159
|
+
input: { workItemIds: [t] }
|
|
160
|
+
} });
|
|
161
|
+
} catch (e) {
|
|
162
|
+
p(e instanceof Error ? e.message : "The work item could not be removed.");
|
|
163
|
+
} finally {
|
|
164
|
+
h(null);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return /* @__PURE__ */ f("div", {
|
|
168
|
+
className: "space-y-3",
|
|
169
|
+
"data-testid": "sprint-work-items",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ f("div", {
|
|
172
|
+
className: "flex items-center justify-between",
|
|
173
|
+
children: [/* @__PURE__ */ f("p", {
|
|
174
|
+
className: "text-sm text-muted-foreground",
|
|
175
|
+
children: [
|
|
176
|
+
i.length,
|
|
177
|
+
" work item",
|
|
178
|
+
i.length === 1 ? "" : "s"
|
|
179
|
+
]
|
|
180
|
+
}), !a && /* @__PURE__ */ d(J, {
|
|
181
|
+
sprintId: e,
|
|
182
|
+
projectId: t
|
|
183
|
+
})]
|
|
184
|
+
}),
|
|
185
|
+
a && /* @__PURE__ */ f("p", {
|
|
186
|
+
className: "text-xs text-muted-foreground",
|
|
187
|
+
children: [
|
|
188
|
+
"This sprint is ",
|
|
189
|
+
K(r),
|
|
190
|
+
"; its work items can no longer be changed."
|
|
191
|
+
]
|
|
192
|
+
}),
|
|
193
|
+
u && /* @__PURE__ */ d(B, { message: u }),
|
|
194
|
+
i.length === 0 ? /* @__PURE__ */ d("p", {
|
|
195
|
+
className: "text-sm text-muted-foreground text-center py-4",
|
|
196
|
+
children: "No work items in this sprint yet."
|
|
197
|
+
}) : /* @__PURE__ */ d("ul", {
|
|
198
|
+
className: "space-y-1",
|
|
199
|
+
children: i.map((e) => /* @__PURE__ */ f("li", {
|
|
200
|
+
className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
|
|
201
|
+
children: [/* @__PURE__ */ f("span", {
|
|
202
|
+
className: "min-w-0 truncate",
|
|
203
|
+
children: [/* @__PURE__ */ d("span", {
|
|
204
|
+
className: "text-muted-foreground mr-2",
|
|
205
|
+
children: e.key
|
|
206
|
+
}), e.title]
|
|
207
|
+
}), /* @__PURE__ */ f("span", {
|
|
208
|
+
className: "flex items-center gap-2 shrink-0",
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ d(S, {
|
|
211
|
+
variant: "secondary",
|
|
212
|
+
className: "text-xs capitalize",
|
|
213
|
+
children: K(e.status)
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ f("span", {
|
|
216
|
+
className: "text-xs text-muted-foreground",
|
|
217
|
+
children: [e.storyPoints ?? 0, " pts"]
|
|
218
|
+
}),
|
|
219
|
+
!a && /* @__PURE__ */ d(C, {
|
|
220
|
+
size: "sm",
|
|
221
|
+
variant: "ghost",
|
|
222
|
+
"aria-label": `Remove ${e.key} from sprint`,
|
|
223
|
+
disabled: c.loading,
|
|
224
|
+
onClick: () => g(e.id),
|
|
225
|
+
children: m === e.id ? "…" : /* @__PURE__ */ d(o, { className: "h-4 w-4" })
|
|
226
|
+
})
|
|
227
|
+
]
|
|
228
|
+
})]
|
|
229
|
+
}, e.id))
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function J({ sprintId: r, projectId: i }) {
|
|
235
|
+
let { workspaceId: o } = e(), [s, c] = n(!1), [p, m] = n(""), h = t(p.trim()), [g, _] = n(/* @__PURE__ */ new Set()), [v, y] = n(null), { data: b, loading: x } = u(G, {
|
|
236
|
+
variables: {
|
|
237
|
+
workspaceId: o ?? "",
|
|
238
|
+
filter: {
|
|
239
|
+
projectId: i,
|
|
240
|
+
...h ? { search: h } : {}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
skip: !s || !o,
|
|
244
|
+
fetchPolicy: "network-only"
|
|
245
|
+
}), [N, P] = l(U, {
|
|
246
|
+
refetchQueries: I,
|
|
247
|
+
awaitRefetchQueries: !0
|
|
248
|
+
}), F = (b?.planmagnetWorkItems.items ?? []).filter((e) => e.sprintId !== r), L = b?.planmagnetWorkItems.totalCount ?? 0, R = (e, t) => _((n) => {
|
|
249
|
+
let r = new Set(n);
|
|
250
|
+
return t ? r.add(e) : r.delete(e), r;
|
|
251
|
+
}), z = () => {
|
|
252
|
+
_(/* @__PURE__ */ new Set()), m(""), y(null);
|
|
253
|
+
};
|
|
254
|
+
return /* @__PURE__ */ f(E, {
|
|
255
|
+
open: s,
|
|
256
|
+
onOpenChange: (e) => {
|
|
257
|
+
c(e), e || z();
|
|
258
|
+
},
|
|
259
|
+
children: [/* @__PURE__ */ d(M, {
|
|
260
|
+
asChild: !0,
|
|
261
|
+
children: /* @__PURE__ */ f(C, {
|
|
262
|
+
size: "sm",
|
|
263
|
+
variant: "outline",
|
|
264
|
+
children: [/* @__PURE__ */ d(a, { className: "mr-1 h-4 w-4" }), "Add work items"]
|
|
265
|
+
})
|
|
266
|
+
}), /* @__PURE__ */ f(D, {
|
|
267
|
+
className: "max-w-lg",
|
|
268
|
+
children: [
|
|
269
|
+
/* @__PURE__ */ f(A, { children: [/* @__PURE__ */ d(j, { children: "Add work items" }), /* @__PURE__ */ d(O, { children: "Work items from this sprint's project. An item already in another sprint moves here." })] }),
|
|
270
|
+
/* @__PURE__ */ d(T, {
|
|
271
|
+
value: p,
|
|
272
|
+
onChange: (e) => m(e.target.value),
|
|
273
|
+
placeholder: "Search by title",
|
|
274
|
+
"aria-label": "Search work items"
|
|
275
|
+
}),
|
|
276
|
+
/* @__PURE__ */ f("div", {
|
|
277
|
+
className: "max-h-80 overflow-y-auto space-y-1",
|
|
278
|
+
"data-testid": "sprint-add-candidates",
|
|
279
|
+
children: [
|
|
280
|
+
x && /* @__PURE__ */ d("p", {
|
|
281
|
+
className: "text-sm text-muted-foreground py-4 text-center",
|
|
282
|
+
children: "Loading…"
|
|
283
|
+
}),
|
|
284
|
+
!x && F.length === 0 && /* @__PURE__ */ d("p", {
|
|
285
|
+
className: "text-sm text-muted-foreground py-4 text-center",
|
|
286
|
+
children: h ? "No matching work items." : "Every work item in this project is already in this sprint."
|
|
287
|
+
}),
|
|
288
|
+
F.map((e) => {
|
|
289
|
+
let t = `sprint-add-${e.id}`;
|
|
290
|
+
return /* @__PURE__ */ f("label", {
|
|
291
|
+
htmlFor: t,
|
|
292
|
+
className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
|
|
293
|
+
children: [
|
|
294
|
+
/* @__PURE__ */ d(w, {
|
|
295
|
+
id: t,
|
|
296
|
+
checked: g.has(e.id),
|
|
297
|
+
onCheckedChange: (t) => R(e.id, t === !0)
|
|
298
|
+
}),
|
|
299
|
+
/* @__PURE__ */ f("span", {
|
|
300
|
+
className: "min-w-0 flex-1 truncate",
|
|
301
|
+
children: [/* @__PURE__ */ d("span", {
|
|
302
|
+
className: "text-muted-foreground mr-2",
|
|
303
|
+
children: e.key
|
|
304
|
+
}), e.title]
|
|
305
|
+
}),
|
|
306
|
+
e.sprintId && /* @__PURE__ */ d("span", {
|
|
307
|
+
className: "text-xs text-muted-foreground shrink-0",
|
|
308
|
+
children: "in another sprint"
|
|
309
|
+
}),
|
|
310
|
+
/* @__PURE__ */ d(S, {
|
|
311
|
+
variant: "secondary",
|
|
312
|
+
className: "text-xs capitalize shrink-0",
|
|
313
|
+
children: K(e.status)
|
|
314
|
+
})
|
|
315
|
+
]
|
|
316
|
+
}, e.id);
|
|
317
|
+
}),
|
|
318
|
+
L > 100 && /* @__PURE__ */ f("p", {
|
|
319
|
+
className: "text-xs text-muted-foreground pt-2",
|
|
320
|
+
children: [
|
|
321
|
+
"Showing the first 100 of ",
|
|
322
|
+
L,
|
|
323
|
+
". Search to narrow the list."
|
|
324
|
+
]
|
|
325
|
+
})
|
|
326
|
+
]
|
|
327
|
+
}),
|
|
328
|
+
v && /* @__PURE__ */ d(B, { message: v }),
|
|
329
|
+
/* @__PURE__ */ d(k, { children: /* @__PURE__ */ d(C, {
|
|
330
|
+
onClick: async () => {
|
|
331
|
+
y(null);
|
|
332
|
+
try {
|
|
333
|
+
await N({ variables: {
|
|
334
|
+
id: r,
|
|
335
|
+
input: { workItemIds: [...g] }
|
|
336
|
+
} }), z(), c(!1);
|
|
337
|
+
} catch (e) {
|
|
338
|
+
y(e instanceof Error ? e.message : "The work items could not be added.");
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
disabled: g.size === 0 || P.loading,
|
|
342
|
+
"data-testid": "sprint-add-confirm",
|
|
343
|
+
children: P.loading ? "Adding…" : `Add ${g.size} work item${g.size === 1 ? "" : "s"}`
|
|
344
|
+
}) })
|
|
345
|
+
]
|
|
346
|
+
})]
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
var Y = c`
|
|
350
|
+
mutation SprintControlsCreate($input: PlanmagnetCreateSprintInput!) {
|
|
351
|
+
planmagnetCreateSprint(input: $input) {
|
|
352
|
+
id
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
`, X = c`
|
|
356
|
+
query SprintControlsProjects($workspaceId: ID!) {
|
|
357
|
+
planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {
|
|
358
|
+
items {
|
|
359
|
+
id
|
|
360
|
+
name
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
`, Z = {
|
|
365
|
+
name: "",
|
|
366
|
+
projectId: "",
|
|
367
|
+
startDate: "",
|
|
368
|
+
endDate: "",
|
|
369
|
+
goal: "",
|
|
370
|
+
capacity: ""
|
|
371
|
+
};
|
|
372
|
+
function Q({ onCreated: t }) {
|
|
373
|
+
let { workspaceId: r } = e(), [i, o] = n(!1), [s, c] = n(Z), [p, m] = n(null), { data: h } = u(X, {
|
|
374
|
+
variables: { workspaceId: r ?? "" },
|
|
375
|
+
skip: !i || !r
|
|
376
|
+
}), g = h?.planmagnetProjects.items ?? [], _ = s.projectId || g[0]?.id || "", [v, { loading: y }] = l(Y, {
|
|
377
|
+
refetchQueries: I,
|
|
378
|
+
awaitRefetchQueries: !0
|
|
379
|
+
}), b = s.capacity.trim() === "" ? void 0 : Number(s.capacity), x = b !== void 0 && (!Number.isInteger(b) || b < 0), S = !!(s.startDate && s.endDate && s.endDate < s.startDate), w = s.name.trim() !== "" && _ !== "" && !x && !S && !y;
|
|
380
|
+
return /* @__PURE__ */ f(E, {
|
|
381
|
+
open: i,
|
|
382
|
+
onOpenChange: (e) => {
|
|
383
|
+
o(e), e || m(null);
|
|
384
|
+
},
|
|
385
|
+
children: [/* @__PURE__ */ d(M, {
|
|
386
|
+
asChild: !0,
|
|
387
|
+
children: /* @__PURE__ */ f(C, {
|
|
388
|
+
size: "sm",
|
|
389
|
+
children: [/* @__PURE__ */ d(a, { className: "mr-1 h-4 w-4" }), "New sprint"]
|
|
390
|
+
})
|
|
391
|
+
}), /* @__PURE__ */ f(D, {
|
|
392
|
+
className: "max-w-lg",
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ f(A, { children: [/* @__PURE__ */ d(j, { children: "New sprint" }), /* @__PURE__ */ d(O, { children: "The sprint starts in Planning. Start it when it is ready." })] }),
|
|
395
|
+
/* @__PURE__ */ f("div", {
|
|
396
|
+
className: "space-y-3",
|
|
397
|
+
children: [
|
|
398
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
399
|
+
htmlFor: "new-sprint-name",
|
|
400
|
+
className: "text-sm font-medium",
|
|
401
|
+
children: "Name"
|
|
402
|
+
}), /* @__PURE__ */ d(T, {
|
|
403
|
+
id: "new-sprint-name",
|
|
404
|
+
className: "mt-1",
|
|
405
|
+
value: s.name,
|
|
406
|
+
onChange: (e) => c({
|
|
407
|
+
...s,
|
|
408
|
+
name: e.target.value
|
|
409
|
+
})
|
|
410
|
+
})] }),
|
|
411
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
412
|
+
htmlFor: "new-sprint-project",
|
|
413
|
+
className: "text-sm font-medium",
|
|
414
|
+
children: "Project"
|
|
415
|
+
}), /* @__PURE__ */ f("select", {
|
|
416
|
+
id: "new-sprint-project",
|
|
417
|
+
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
418
|
+
value: _,
|
|
419
|
+
onChange: (e) => c({
|
|
420
|
+
...s,
|
|
421
|
+
projectId: e.target.value
|
|
422
|
+
}),
|
|
423
|
+
children: [g.length === 0 && /* @__PURE__ */ d("option", {
|
|
424
|
+
value: "",
|
|
425
|
+
children: "No projects"
|
|
426
|
+
}), g.map((e) => /* @__PURE__ */ d("option", {
|
|
427
|
+
value: e.id,
|
|
428
|
+
children: e.name
|
|
429
|
+
}, e.id))]
|
|
430
|
+
})] }),
|
|
431
|
+
/* @__PURE__ */ f("div", {
|
|
432
|
+
className: "grid grid-cols-2 gap-3",
|
|
433
|
+
children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
434
|
+
htmlFor: "new-sprint-start",
|
|
435
|
+
className: "text-sm font-medium",
|
|
436
|
+
children: "Start date"
|
|
437
|
+
}), /* @__PURE__ */ d(T, {
|
|
438
|
+
id: "new-sprint-start",
|
|
439
|
+
type: "date",
|
|
440
|
+
className: "mt-1",
|
|
441
|
+
value: s.startDate,
|
|
442
|
+
onChange: (e) => c({
|
|
443
|
+
...s,
|
|
444
|
+
startDate: e.target.value
|
|
445
|
+
})
|
|
446
|
+
})] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
447
|
+
htmlFor: "new-sprint-end",
|
|
448
|
+
className: "text-sm font-medium",
|
|
449
|
+
children: "End date"
|
|
450
|
+
}), /* @__PURE__ */ d(T, {
|
|
451
|
+
id: "new-sprint-end",
|
|
452
|
+
type: "date",
|
|
453
|
+
className: "mt-1",
|
|
454
|
+
value: s.endDate,
|
|
455
|
+
onChange: (e) => c({
|
|
456
|
+
...s,
|
|
457
|
+
endDate: e.target.value
|
|
458
|
+
})
|
|
459
|
+
})] })]
|
|
460
|
+
}),
|
|
461
|
+
S && /* @__PURE__ */ d(B, { message: "The end date is before the start date." }),
|
|
462
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
463
|
+
htmlFor: "new-sprint-capacity",
|
|
464
|
+
className: "text-sm font-medium",
|
|
465
|
+
children: "Capacity (story points)"
|
|
466
|
+
}), /* @__PURE__ */ d(T, {
|
|
467
|
+
id: "new-sprint-capacity",
|
|
468
|
+
type: "number",
|
|
469
|
+
min: 0,
|
|
470
|
+
className: "mt-1",
|
|
471
|
+
value: s.capacity,
|
|
472
|
+
onChange: (e) => c({
|
|
473
|
+
...s,
|
|
474
|
+
capacity: e.target.value
|
|
475
|
+
})
|
|
476
|
+
})] }),
|
|
477
|
+
x && /* @__PURE__ */ d(B, { message: "Capacity must be a whole number, 0 or more." }),
|
|
478
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
|
|
479
|
+
htmlFor: "new-sprint-goal",
|
|
480
|
+
className: "text-sm font-medium",
|
|
481
|
+
children: "Goal"
|
|
482
|
+
}), /* @__PURE__ */ d(T, {
|
|
483
|
+
id: "new-sprint-goal",
|
|
484
|
+
className: "mt-1",
|
|
485
|
+
value: s.goal,
|
|
486
|
+
onChange: (e) => c({
|
|
487
|
+
...s,
|
|
488
|
+
goal: e.target.value
|
|
489
|
+
})
|
|
490
|
+
})] })
|
|
491
|
+
]
|
|
492
|
+
}),
|
|
493
|
+
p && /* @__PURE__ */ d(B, { message: p }),
|
|
494
|
+
/* @__PURE__ */ d(k, { children: /* @__PURE__ */ d(C, {
|
|
495
|
+
onClick: async () => {
|
|
496
|
+
m(null);
|
|
497
|
+
try {
|
|
498
|
+
let e = (await v({ variables: { input: {
|
|
499
|
+
name: s.name.trim(),
|
|
500
|
+
projectId: _,
|
|
501
|
+
startDate: s.startDate || void 0,
|
|
502
|
+
endDate: s.endDate || void 0,
|
|
503
|
+
goal: s.goal.trim() || void 0,
|
|
504
|
+
capacity: b
|
|
505
|
+
} } })).data?.planmagnetCreateSprint.id;
|
|
506
|
+
c(Z), o(!1), e && t(e);
|
|
507
|
+
} catch (e) {
|
|
508
|
+
m(e instanceof Error ? e.message : "The sprint could not be created.");
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
disabled: !w,
|
|
512
|
+
"data-testid": "new-sprint-submit",
|
|
513
|
+
children: y ? "Creating…" : "Create sprint"
|
|
514
|
+
}) })
|
|
515
|
+
]
|
|
516
|
+
})]
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
520
|
+
export { Q as NewSprintDialog, V as SprintLifecycleActions, q as SprintWorkItemsPanel };
|
|
521
|
+
|
|
522
|
+
//# sourceMappingURL=SprintControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SprintControls.js","names":[],"sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Badge,\n Button,\n Checkbox,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Play, Plus, X, XCircle } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Sprint lifecycle + membership controls.\n *\n * The buttons mirror the service's transition table exactly\n * (wspace-planmagnet-svc sprint.service.ts `TRANSITIONS`):\n * start PLANNING -> ACTIVE\n * complete ACTIVE | REVIEW -> COMPLETED\n * cancel PLANNING|ACTIVE|REVIEW -> CANCELLED\n * A COMPLETED or CANCELLED sprint is finished: no transitions, and its work\n * items can no longer be added or removed. The server enforces all of this;\n * the UI only hides what would be refused, and still surfaces a refusal (a\n * concurrent change can make a shown button stale).\n */\n\nexport type SprintStatus = 'PLANNING' | 'ACTIVE' | 'REVIEW' | 'COMPLETED' | 'CANCELLED';\nexport type SprintAction = 'start' | 'complete' | 'cancel';\n\nconst ALLOWED: Record<SprintAction, readonly SprintStatus[]> = {\n start: ['PLANNING'],\n complete: ['ACTIVE', 'REVIEW'],\n cancel: ['PLANNING', 'ACTIVE', 'REVIEW'],\n};\n\nexport function sprintActionsFor(status: string): SprintAction[] {\n return (Object.keys(ALLOWED) as SprintAction[]).filter((action) =>\n (ALLOWED[action] as readonly string[]).includes(status)\n );\n}\n\nexport function isSprintFinished(status: string): boolean {\n return status === 'COMPLETED' || status === 'CANCELLED';\n}\n\n/** Every query that renders sprint status or membership. */\nconst SPRINT_QUERIES = ['GetSprints', 'GetSprintDetail', 'GetSprintForEdit', 'GetDashboardData'];\n\nconst START_SPRINT = gql`\n mutation SprintControlsStart($id: ID!) {\n planmagnetStartSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst COMPLETE_SPRINT = gql`\n mutation SprintControlsComplete($id: ID!) {\n planmagnetCompleteSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst CANCEL_SPRINT = gql`\n mutation SprintControlsCancel($id: ID!) {\n planmagnetCancelSprint(id: $id) {\n id\n status\n }\n }\n`;\n\nfunction ErrorLine({ message }: { message: string }) {\n return (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {message}\n </p>\n );\n}\n\nexport function SprintLifecycleActions({\n sprintId,\n sprintName,\n status,\n}: {\n sprintId: string;\n sprintName: string;\n status: string;\n}) {\n const options = { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true };\n const [start, startState] = useMutation(START_SPRINT, options);\n const [complete, completeState] = useMutation(COMPLETE_SPRINT, options);\n const [cancel, cancelState] = useMutation(CANCEL_SPRINT, options);\n const [error, setError] = useState<string | null>(null);\n\n const busy = startState.loading || completeState.loading || cancelState.loading;\n const actions = sprintActionsFor(status);\n if (actions.length === 0) return null;\n\n const run = async (mutate: typeof start) => {\n setError(null);\n try {\n await mutate({ variables: { id: sprintId } });\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be updated.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\" data-testid=\"sprint-lifecycle-actions\">\n <div className=\"flex gap-2\">\n {actions.includes('start') && (\n <Button size=\"sm\" onClick={() => run(start)} disabled={busy}>\n <Play className=\"mr-1 h-4 w-4\" />\n Start sprint\n </Button>\n )}\n {actions.includes('complete') && (\n <ConfirmButton\n label=\"Complete sprint\"\n icon={<CheckCircle2 className=\"mr-1 h-4 w-4\" />}\n title={`Complete ${sprintName}?`}\n description=\"The sprint is closed and its work items can no longer be changed from it. This cannot be undone.\"\n confirmLabel=\"Complete sprint\"\n disabled={busy}\n onConfirm={() => run(complete)}\n />\n )}\n {actions.includes('cancel') && (\n <ConfirmButton\n label=\"Cancel sprint\"\n variant=\"outline\"\n icon={<XCircle className=\"mr-1 h-4 w-4\" />}\n title={`Cancel ${sprintName}?`}\n description=\"The sprint is abandoned and cannot be restarted. Its work items stay in it for the record. This cannot be undone.\"\n confirmLabel=\"Cancel sprint\"\n destructive\n disabled={busy}\n onConfirm={() => run(cancel)}\n />\n )}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nfunction ConfirmButton({\n label,\n icon,\n title,\n description,\n confirmLabel,\n onConfirm,\n disabled,\n destructive = false,\n variant = 'default',\n}: {\n label: string;\n icon: React.ReactNode;\n title: string;\n description: string;\n confirmLabel: string;\n onConfirm: () => void;\n disabled: boolean;\n destructive?: boolean;\n variant?: 'default' | 'outline';\n}) {\n return (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant={variant} disabled={disabled}>\n {icon}\n {label}\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n <AlertDialogDescription>{description}</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n onClick={onConfirm}\n className={\n destructive\n ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'\n : ''\n }\n >\n {confirmLabel}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n// ─── Work items in the sprint ──────────────────────────────────────────────\n\nconst ADD_ITEMS = gql`\n mutation SprintControlsAddItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetAddWorkItemsToSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst REMOVE_ITEMS = gql`\n mutation SprintControlsRemoveItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetRemoveWorkItemsFromSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst PROJECT_WORK_ITEMS = gql`\n query SprintControlsCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n totalCount\n items {\n id\n key\n title\n status\n storyPoints\n sprintId\n }\n }\n }\n`;\n\nexport interface SprintWorkItem {\n id: string;\n key: string;\n title?: string | null;\n status: string;\n storyPoints: number | null;\n}\n\ninterface CandidateItem extends SprintWorkItem {\n sprintId: string | null;\n}\n\nconst statusLabel = (status: string) => status.toLowerCase().replace(/_/g, ' ');\n\nexport function SprintWorkItemsPanel({\n sprintId,\n projectId,\n status,\n items,\n}: {\n sprintId: string;\n projectId: string;\n status: string;\n items: SprintWorkItem[];\n}) {\n const finished = isSprintFinished(status);\n const [remove, removeState] = useMutation(REMOVE_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (workItemId: string) => {\n setError(null);\n setRemovingId(workItemId);\n try {\n await remove({ variables: { id: sprintId, input: { workItemIds: [workItemId] } } });\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work item could not be removed.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"sprint-work-items\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {items.length} work item{items.length === 1 ? '' : 's'}\n </p>\n {!finished && <AddWorkItemsDialog sprintId={sprintId} projectId={projectId} />}\n </div>\n {finished && (\n <p className=\"text-xs text-muted-foreground\">\n This sprint is {statusLabel(status)}; its work items can no longer be changed.\n </p>\n )}\n {error && <ErrorLine message={error} />}\n {items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No work items in this sprint yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {items.map((item) => (\n <li\n key={item.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <span className=\"min-w-0 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n <span className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"secondary\" className=\"text-xs capitalize\">\n {statusLabel(item.status)}\n </Badge>\n <span className=\"text-xs text-muted-foreground\">{item.storyPoints ?? 0} pts</span>\n {!finished && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Remove ${item.key} from sprint`}\n disabled={removeState.loading}\n onClick={() => handleRemove(item.id)}\n >\n {removingId === item.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n )}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddWorkItemsDialog({ sprintId, projectId }: { sprintId: string; projectId: string }) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search.trim());\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetWorkItems: { totalCount: number; items: CandidateItem[] };\n }>(PROJECT_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { projectId, ...(deferredSearch ? { search: deferredSearch } : {}) },\n },\n skip: !open || !workspaceId,\n fetchPolicy: 'network-only',\n });\n const [add, addState] = useMutation(ADD_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const candidates = (data?.planmagnetWorkItems.items ?? []).filter(\n (item) => item.sprintId !== sprintId\n );\n const totalCount = data?.planmagnetWorkItems.totalCount ?? 0;\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setSearch('');\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await add({ variables: { id: sprintId, input: { workItemIds: [...selected] } } });\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work items could not be added.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add work items\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add work items</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Work items from this sprint's project. An item already in another sprint moves\n here.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by title\"\n aria-label=\"Search work items\"\n />\n <div className=\"max-h-80 overflow-y-auto space-y-1\" data-testid=\"sprint-add-candidates\">\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {deferredSearch\n ? 'No matching work items.'\n : 'Every work item in this project is already in this sprint.'}\n </p>\n )}\n {candidates.map((item) => {\n const inputId = `sprint-add-${item.id}`;\n return (\n <label\n key={item.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(item.id)}\n onCheckedChange={(checked) => toggle(item.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n {item.sprintId && (\n <span className=\"text-xs text-muted-foreground shrink-0\">in another sprint</span>\n )}\n <Badge variant=\"secondary\" className=\"text-xs capitalize shrink-0\">\n {statusLabel(item.status)}\n </Badge>\n </label>\n );\n })}\n {totalCount > 100 && (\n <p className=\"text-xs text-muted-foreground pt-2\">\n Showing the first 100 of {totalCount}. Search to narrow the list.\n </p>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={handleAdd}\n disabled={selected.size === 0 || addState.loading}\n data-testid=\"sprint-add-confirm\"\n >\n {addState.loading\n ? 'Adding…'\n : `Add ${selected.size} work item${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── New sprint ─────────────────────────────────────────────────────────────\n\nconst CREATE_SPRINT = gql`\n mutation SprintControlsCreate($input: PlanmagnetCreateSprintInput!) {\n planmagnetCreateSprint(input: $input) {\n id\n }\n }\n`;\nconst SPRINT_PROJECTS = gql`\n query SprintControlsProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n }\n }\n`;\n\nconst EMPTY_FORM = { name: '', projectId: '', startDate: '', endDate: '', goal: '', capacity: '' };\n\nexport function NewSprintDialog({ onCreated }: { onCreated: (sprintId: string) => void }) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n const { data: projectsData } = useQuery<{\n planmagnetProjects: { items: Array<{ id: string; name: string }> };\n }>(SPRINT_PROJECTS, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const projects = projectsData?.planmagnetProjects.items ?? [];\n const projectId = form.projectId || projects[0]?.id || '';\n\n const [create, { loading }] = useMutation<{ planmagnetCreateSprint: { id: string } }>(\n CREATE_SPRINT,\n { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true }\n );\n\n const capacity = form.capacity.trim() === '' ? undefined : Number(form.capacity);\n const invalidCapacity = capacity !== undefined && (!Number.isInteger(capacity) || capacity < 0);\n const invalidDates = Boolean(form.startDate && form.endDate && form.endDate < form.startDate);\n const canSubmit =\n form.name.trim() !== '' && projectId !== '' && !invalidCapacity && !invalidDates && !loading;\n\n const handleCreate = async () => {\n setError(null);\n try {\n const result = await create({\n variables: {\n input: {\n name: form.name.trim(),\n projectId,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n goal: form.goal.trim() || undefined,\n capacity,\n },\n },\n });\n const id = result.data?.planmagnetCreateSprint.id;\n setForm(EMPTY_FORM);\n setOpen(false);\n if (id) onCreated(id);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be created.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\">\n <Plus className=\"mr-1 h-4 w-4\" />\n New sprint\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New sprint</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n The sprint starts in Planning. Start it when it is ready.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"new-sprint-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"new-sprint-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"new-sprint-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <select\n id=\"new-sprint-project\"\n className={field}\n value={projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n {projects.length === 0 && <option value=\"\">No projects</option>}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"new-sprint-start\" className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id=\"new-sprint-start\"\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"new-sprint-end\" className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id=\"new-sprint-end\"\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor=\"new-sprint-capacity\" className=\"text-sm font-medium\">\n Capacity (story points)\n </label>\n <Input\n id=\"new-sprint-capacity\"\n type=\"number\"\n min={0}\n className=\"mt-1\"\n value={form.capacity}\n onChange={(e) => setForm({ ...form, capacity: e.target.value })}\n />\n </div>\n {invalidCapacity && <ErrorLine message=\"Capacity must be a whole number, 0 or more.\" />}\n <div>\n <label htmlFor=\"new-sprint-goal\" className=\"text-sm font-medium\">\n Goal\n </label>\n <Input\n id=\"new-sprint-goal\"\n className=\"mt-1\"\n value={form.goal}\n onChange={(e) => setForm({ ...form, goal: e.target.value })}\n />\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button onClick={handleCreate} disabled={!canSubmit} data-testid=\"new-sprint-submit\">\n {loading ? 'Creating…' : 'Create sprint'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|