@burdenoff/microfe-planmagnet 2026.927.5 → 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.
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Whole days in the current status, or null when unknown. */
|
|
2
|
+
export declare function daysInStatus(statusSince: string | null, now: number): number | null;
|
|
1
3
|
export declare function ProjectBoard({ projectId, projectName, }: {
|
|
2
4
|
projectId: string;
|
|
3
5
|
projectName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectBoard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectBoard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"names":[],"mappings":"AAiNA,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnF;AAgCD,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,2CAoPA"}
|
|
@@ -4,11 +4,11 @@ import { throwOnError as r } from "../utils/mutationResult.js";
|
|
|
4
4
|
import { useState as i } from "react";
|
|
5
5
|
import { ChevronLeft as a, ChevronRight as o, LayoutGrid as s, Pencil as c, Plus as l } from "lucide-react";
|
|
6
6
|
import { gql as u } from "@apollo/client";
|
|
7
|
-
import {
|
|
8
|
-
import { jsx as
|
|
9
|
-
import { AlertDialog as
|
|
7
|
+
import { useApolloClient as d, useMutation as f, useQuery as p } from "@apollo/client/react";
|
|
8
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
9
|
+
import { AlertDialog as g, AlertDialogAction as _, AlertDialogCancel as v, AlertDialogContent as y, AlertDialogDescription as b, AlertDialogFooter as x, AlertDialogHeader as ee, AlertDialogTitle as S, AlertDialogTrigger as C, Button as w, Card as T, CardContent as E, Input as D, ResponsiveDialog as te, ResponsiveDialogContent as O, ResponsiveDialogDescription as k, ResponsiveDialogFooter as ne, ResponsiveDialogHeader as re, ResponsiveDialogTitle as A, ResponsiveDialogTrigger as j } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/planmagnet/board/ProjectBoard.tsx
|
|
11
|
-
var
|
|
11
|
+
var M = [
|
|
12
12
|
"BACKLOG",
|
|
13
13
|
"TODO",
|
|
14
14
|
"IN_PROGRESS",
|
|
@@ -16,7 +16,7 @@ var te = [
|
|
|
16
16
|
"TESTING",
|
|
17
17
|
"DONE",
|
|
18
18
|
"CANCELLED"
|
|
19
|
-
],
|
|
19
|
+
], N = (e) => e.charAt(0) + e.slice(1).toLowerCase().replace(/_/g, " "), P = 100, F = u`
|
|
20
20
|
query ProjectBoards($workspaceId: ID!, $projectId: String) {
|
|
21
21
|
planmagnetBoards(
|
|
22
22
|
workspaceId: $workspaceId
|
|
@@ -30,7 +30,30 @@ var te = [
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
`,
|
|
33
|
+
`, I = u`
|
|
34
|
+
query ProjectBoardColumnPage($boardId: ID!, $columnId: ID!, $after: String) {
|
|
35
|
+
planmagnetBoardColumnCards(
|
|
36
|
+
boardId: $boardId
|
|
37
|
+
columnId: $columnId
|
|
38
|
+
pagination: { first: 100, after: $after }
|
|
39
|
+
) {
|
|
40
|
+
pageInfo {
|
|
41
|
+
hasNextPage
|
|
42
|
+
endCursor
|
|
43
|
+
}
|
|
44
|
+
items {
|
|
45
|
+
id
|
|
46
|
+
key
|
|
47
|
+
title
|
|
48
|
+
status
|
|
49
|
+
priority
|
|
50
|
+
storyPoints
|
|
51
|
+
assigneeId
|
|
52
|
+
statusSince
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`, ie = u`
|
|
34
57
|
query ProjectBoard($id: ID!) {
|
|
35
58
|
planmagnetBoard(id: $id) {
|
|
36
59
|
id
|
|
@@ -44,6 +67,10 @@ var te = [
|
|
|
44
67
|
sortOrder
|
|
45
68
|
cards(pagination: { first: 100 }) {
|
|
46
69
|
totalCount
|
|
70
|
+
pageInfo {
|
|
71
|
+
hasNextPage
|
|
72
|
+
endCursor
|
|
73
|
+
}
|
|
47
74
|
items {
|
|
48
75
|
id
|
|
49
76
|
key
|
|
@@ -52,434 +79,674 @@ var te = [
|
|
|
52
79
|
priority
|
|
53
80
|
storyPoints
|
|
54
81
|
assigneeId
|
|
82
|
+
statusSince
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
}
|
|
59
87
|
}
|
|
60
|
-
`,
|
|
88
|
+
`, L = u`
|
|
61
89
|
mutation ProjectBoardMoveCard($boardId: ID!, $input: PlanmagnetMoveCardInput!) {
|
|
62
90
|
planmagnetMoveCard(boardId: $boardId, input: $input) {
|
|
63
91
|
id
|
|
64
92
|
}
|
|
65
93
|
}
|
|
66
|
-
`,
|
|
94
|
+
`, R = u`
|
|
67
95
|
mutation ProjectBoardCreate($input: PlanmagnetCreateBoardInput!) {
|
|
68
96
|
planmagnetCreateBoard(input: $input) {
|
|
69
97
|
id
|
|
70
98
|
}
|
|
71
99
|
}
|
|
72
|
-
`,
|
|
100
|
+
`, z = u`
|
|
73
101
|
mutation ProjectBoardCreateColumn($input: PlanmagnetCreateBoardColumnInput!) {
|
|
74
102
|
planmagnetCreateBoardColumn(input: $input) {
|
|
75
103
|
id
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
|
-
`,
|
|
106
|
+
`, B = u`
|
|
79
107
|
mutation ProjectBoardUpdateColumn($id: ID!, $input: PlanmagnetUpdateBoardColumnInput!) {
|
|
80
108
|
planmagnetUpdateBoardColumn(id: $id, input: $input) {
|
|
81
109
|
id
|
|
82
110
|
}
|
|
83
111
|
}
|
|
84
|
-
`,
|
|
112
|
+
`, V = u`
|
|
85
113
|
mutation ProjectBoardDeleteColumn($id: ID!) {
|
|
86
114
|
planmagnetDeleteBoardColumn(id: $id)
|
|
87
115
|
}
|
|
88
|
-
`,
|
|
116
|
+
`, H = u`
|
|
89
117
|
mutation ProjectBoardReorder($boardId: ID!, $input: PlanmagnetReorderColumnsInput!) {
|
|
90
118
|
planmagnetReorderColumns(boardId: $boardId, input: $input) {
|
|
91
119
|
id
|
|
92
120
|
}
|
|
93
121
|
}
|
|
94
|
-
`,
|
|
122
|
+
`, U = [
|
|
95
123
|
"ProjectBoard",
|
|
96
124
|
"ProjectBoards",
|
|
97
125
|
"GetWorkItems",
|
|
98
126
|
"GetWorkItemCounts",
|
|
99
127
|
"GetDashboardData"
|
|
100
|
-
],
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
], W = [
|
|
129
|
+
"CRITICAL",
|
|
130
|
+
"HIGH",
|
|
131
|
+
"MEDIUM",
|
|
132
|
+
"LOW",
|
|
133
|
+
"NONE"
|
|
134
|
+
], G = 864e5;
|
|
135
|
+
function K(e, t) {
|
|
136
|
+
return e ? Math.max(0, Math.floor((t - new Date(e).getTime()) / G)) : null;
|
|
137
|
+
}
|
|
138
|
+
function q(e) {
|
|
139
|
+
return e >= 14 ? "text-destructive font-medium" : e >= 7 ? "text-status-warning-text" : "text-muted-foreground";
|
|
140
|
+
}
|
|
141
|
+
function J(e, t) {
|
|
142
|
+
return t === "assignee" ? e.assigneeId ?? "" : t === "priority" ? e.priority || "NONE" : "";
|
|
143
|
+
}
|
|
144
|
+
var Y = (e, t) => e instanceof Error ? e.message : t;
|
|
145
|
+
function X({ message: e }) {
|
|
146
|
+
return /* @__PURE__ */ m("p", {
|
|
103
147
|
role: "alert",
|
|
104
148
|
className: "text-sm text-destructive",
|
|
105
149
|
children: e
|
|
106
150
|
});
|
|
107
151
|
}
|
|
108
|
-
function
|
|
109
|
-
let { workspaceId: a } = e(),
|
|
152
|
+
function ae({ projectId: t, projectName: n }) {
|
|
153
|
+
let { workspaceId: a } = e(), o = d(), [c, u] = i(null), [g, _] = i(null), [v] = i(() => Date.now()), [y, b] = i("none"), [x, ee] = i({
|
|
154
|
+
key: null,
|
|
155
|
+
byColumn: {}
|
|
156
|
+
}), [S, C] = i(null), { data: D, loading: te } = p(F, {
|
|
110
157
|
variables: {
|
|
111
158
|
workspaceId: a,
|
|
112
159
|
projectId: t
|
|
113
160
|
},
|
|
114
161
|
skip: !a
|
|
115
|
-
}),
|
|
116
|
-
variables: { id:
|
|
117
|
-
skip: !
|
|
118
|
-
}),
|
|
119
|
-
|
|
162
|
+
}), O = D?.planmagnetBoards.items ?? [], k = (c && O.some((e) => e.id === c) ? c : null) ?? O.find((e) => e.isDefault)?.id ?? O[0]?.id ?? null, { data: ne, loading: re } = p(ie, {
|
|
163
|
+
variables: { id: k },
|
|
164
|
+
skip: !k
|
|
165
|
+
}), A = ne?.planmagnetBoard ?? null, j = x.key === A ? x.byColumn : {}, M = (e) => {
|
|
166
|
+
let t = j[e.id];
|
|
167
|
+
return {
|
|
168
|
+
items: t ? [...e.cards.items, ...t.items] : e.cards.items,
|
|
169
|
+
pageInfo: t ? t.pageInfo : e.cards.pageInfo,
|
|
170
|
+
totalCount: e.cards.totalCount
|
|
171
|
+
};
|
|
172
|
+
}, N = async (e) => {
|
|
173
|
+
let t = M(e).pageInfo.endCursor;
|
|
174
|
+
if (!(!A || !t)) {
|
|
175
|
+
_(null), C(e.id);
|
|
176
|
+
try {
|
|
177
|
+
let n = await o.query({
|
|
178
|
+
query: I,
|
|
179
|
+
variables: {
|
|
180
|
+
boardId: A.id,
|
|
181
|
+
columnId: e.id,
|
|
182
|
+
after: t
|
|
183
|
+
},
|
|
184
|
+
fetchPolicy: "network-only"
|
|
185
|
+
}), r = n.data?.planmagnetBoardColumnCards;
|
|
186
|
+
if (!r) throw Error(n.error?.message ?? "The next cards could not be loaded.");
|
|
187
|
+
let i = j[e.id]?.items ?? [];
|
|
188
|
+
ee({
|
|
189
|
+
key: A,
|
|
190
|
+
byColumn: {
|
|
191
|
+
...j,
|
|
192
|
+
[e.id]: {
|
|
193
|
+
items: [...i, ...r.items],
|
|
194
|
+
pageInfo: r.pageInfo
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} catch (e) {
|
|
199
|
+
_(Y(e, "The next cards could not be loaded."));
|
|
200
|
+
} finally {
|
|
201
|
+
C(null);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}, [P, z] = f(R, {
|
|
205
|
+
refetchQueries: U,
|
|
120
206
|
awaitRefetchQueries: !0
|
|
121
|
-
}), [
|
|
122
|
-
refetchQueries:
|
|
207
|
+
}), [B, V] = f(L, {
|
|
208
|
+
refetchQueries: U,
|
|
123
209
|
awaitRefetchQueries: !0
|
|
124
|
-
}), [
|
|
125
|
-
refetchQueries:
|
|
210
|
+
}), [W] = f(H, {
|
|
211
|
+
refetchQueries: U,
|
|
126
212
|
awaitRefetchQueries: !0
|
|
127
|
-
}),
|
|
128
|
-
|
|
213
|
+
}), G = async (e, t) => {
|
|
214
|
+
_(null);
|
|
129
215
|
try {
|
|
130
216
|
await r(e());
|
|
131
217
|
} catch (e) {
|
|
132
|
-
|
|
218
|
+
_(Y(e, t));
|
|
133
219
|
}
|
|
134
|
-
},
|
|
135
|
-
boardId:
|
|
220
|
+
}, K = (e, t) => G(() => B({ variables: {
|
|
221
|
+
boardId: k,
|
|
136
222
|
input: {
|
|
137
223
|
workItemId: e,
|
|
138
224
|
targetColumnId: t
|
|
139
225
|
}
|
|
140
|
-
} }), "The card could not be moved."),
|
|
141
|
-
if (!
|
|
142
|
-
let n =
|
|
143
|
-
if (!(r < 0 || r >= n.length)) return [n[e], n[r]] = [n[r], n[e]],
|
|
144
|
-
boardId:
|
|
226
|
+
} }), "The card could not be moved."), q = (e, t) => {
|
|
227
|
+
if (!A) return;
|
|
228
|
+
let n = A.columns.map((e) => e.id), r = e + t;
|
|
229
|
+
if (!(r < 0 || r >= n.length)) return [n[e], n[r]] = [n[r], n[e]], G(() => W({ variables: {
|
|
230
|
+
boardId: k,
|
|
145
231
|
input: { columnIds: n }
|
|
146
232
|
} }), "The columns could not be reordered.");
|
|
147
233
|
};
|
|
148
|
-
return
|
|
234
|
+
return te && !D ? /* @__PURE__ */ m("p", {
|
|
149
235
|
className: "text-sm text-muted-foreground p-4",
|
|
150
236
|
children: "Loading board…"
|
|
151
|
-
}) :
|
|
237
|
+
}) : k ? /* @__PURE__ */ h("div", {
|
|
152
238
|
className: "space-y-3",
|
|
153
239
|
"data-testid": "project-board",
|
|
154
240
|
children: [
|
|
155
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ h("div", {
|
|
156
242
|
className: "flex flex-wrap items-center gap-3",
|
|
157
243
|
children: [
|
|
158
|
-
|
|
244
|
+
O.length > 1 && /* @__PURE__ */ m("select", {
|
|
159
245
|
"aria-label": "Board",
|
|
160
246
|
className: "rounded-md border border-input bg-background px-2 py-1 text-sm",
|
|
161
|
-
value:
|
|
162
|
-
onChange: (e) =>
|
|
163
|
-
children:
|
|
247
|
+
value: k,
|
|
248
|
+
onChange: (e) => u(e.target.value),
|
|
249
|
+
children: O.map((e) => /* @__PURE__ */ m("option", {
|
|
164
250
|
value: e.id,
|
|
165
251
|
children: e.name
|
|
166
252
|
}, e.id))
|
|
167
253
|
}),
|
|
168
|
-
|
|
254
|
+
A && /* @__PURE__ */ h("label", {
|
|
255
|
+
className: "flex items-center gap-1 text-sm",
|
|
256
|
+
children: [/* @__PURE__ */ m("span", {
|
|
257
|
+
className: "text-muted-foreground",
|
|
258
|
+
children: "Swimlanes"
|
|
259
|
+
}), /* @__PURE__ */ h("select", {
|
|
260
|
+
"aria-label": "Swimlanes",
|
|
261
|
+
"data-testid": "board-lanes",
|
|
262
|
+
className: "rounded-md border border-input bg-background px-2 py-1 text-sm",
|
|
263
|
+
value: y,
|
|
264
|
+
onChange: (e) => b(e.target.value),
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ m("option", {
|
|
267
|
+
value: "none",
|
|
268
|
+
children: "None"
|
|
269
|
+
}),
|
|
270
|
+
/* @__PURE__ */ m("option", {
|
|
271
|
+
value: "assignee",
|
|
272
|
+
children: "By assignee"
|
|
273
|
+
}),
|
|
274
|
+
/* @__PURE__ */ m("option", {
|
|
275
|
+
value: "priority",
|
|
276
|
+
children: "By priority"
|
|
277
|
+
})
|
|
278
|
+
]
|
|
279
|
+
})]
|
|
280
|
+
}),
|
|
281
|
+
A && A.unplacedCount > 0 && /* @__PURE__ */ h("p", {
|
|
169
282
|
className: "text-xs text-muted-foreground",
|
|
170
283
|
"data-testid": "board-unplaced",
|
|
171
284
|
children: [
|
|
172
|
-
|
|
285
|
+
A.unplacedCount,
|
|
173
286
|
" work item",
|
|
174
|
-
|
|
287
|
+
A.unplacedCount === 1 ? " is" : "s are",
|
|
175
288
|
" not on this board: ",
|
|
176
|
-
|
|
289
|
+
A.unplacedCount === 1 ? "its status has" : "their status has",
|
|
177
290
|
" no column."
|
|
178
291
|
]
|
|
179
292
|
}),
|
|
180
|
-
|
|
293
|
+
A && /* @__PURE__ */ m("div", {
|
|
181
294
|
className: "ml-auto",
|
|
182
|
-
children: /* @__PURE__ */
|
|
295
|
+
children: /* @__PURE__ */ m(ce, { boardId: A.id })
|
|
183
296
|
})
|
|
184
297
|
]
|
|
185
298
|
}),
|
|
186
|
-
|
|
187
|
-
|
|
299
|
+
g && /* @__PURE__ */ m(X, { message: g }),
|
|
300
|
+
re && !A ? /* @__PURE__ */ m("p", {
|
|
188
301
|
className: "text-sm text-muted-foreground p-4",
|
|
189
302
|
children: "Loading board…"
|
|
190
|
-
}) : /* @__PURE__ */ m(
|
|
303
|
+
}) : A && y !== "none" ? /* @__PURE__ */ m(oe, {
|
|
304
|
+
board: A,
|
|
305
|
+
laneBy: y,
|
|
306
|
+
cardsOf: M,
|
|
307
|
+
now: v,
|
|
308
|
+
busy: V.loading,
|
|
309
|
+
loadingMore: S,
|
|
310
|
+
onLoadMore: (e) => void N(e),
|
|
311
|
+
onMove: K
|
|
312
|
+
}) : /* @__PURE__ */ h("div", {
|
|
191
313
|
className: "flex gap-3 overflow-x-auto pb-2",
|
|
192
|
-
children: [
|
|
193
|
-
boardId:
|
|
314
|
+
children: [A?.columns.map((e, t) => /* @__PURE__ */ m(se, {
|
|
315
|
+
boardId: A.id,
|
|
194
316
|
column: e,
|
|
195
|
-
columns:
|
|
317
|
+
columns: A.columns,
|
|
318
|
+
cards: M(e),
|
|
196
319
|
isFirst: t === 0,
|
|
197
|
-
isLast: t ===
|
|
198
|
-
busy:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
320
|
+
isLast: t === A.columns.length - 1,
|
|
321
|
+
busy: V.loading,
|
|
322
|
+
now: v,
|
|
323
|
+
loadingMore: S === e.id,
|
|
324
|
+
onLoadMore: () => void N(e),
|
|
325
|
+
onMove: K,
|
|
326
|
+
onShift: (e) => q(t, e)
|
|
327
|
+
}, e.id)), A && A.columns.length === 0 && /* @__PURE__ */ m("p", {
|
|
202
328
|
className: "text-sm text-muted-foreground p-4",
|
|
203
329
|
children: "This board has no columns. Add one to start placing work."
|
|
204
330
|
})]
|
|
205
331
|
})
|
|
206
332
|
]
|
|
207
|
-
}) : /* @__PURE__ */
|
|
333
|
+
}) : /* @__PURE__ */ m(T, { children: /* @__PURE__ */ h(E, {
|
|
208
334
|
className: "p-8 text-center space-y-3",
|
|
209
335
|
"data-testid": "board-empty",
|
|
210
336
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
/* @__PURE__ */
|
|
337
|
+
/* @__PURE__ */ m(s, { className: "h-8 w-8 mx-auto text-muted-foreground" }),
|
|
338
|
+
/* @__PURE__ */ h("p", {
|
|
213
339
|
className: "text-sm text-muted-foreground",
|
|
214
340
|
children: [n, " has no board yet."]
|
|
215
341
|
}),
|
|
216
|
-
/* @__PURE__ */
|
|
342
|
+
/* @__PURE__ */ h(w, {
|
|
217
343
|
size: "sm",
|
|
218
|
-
disabled:
|
|
219
|
-
onClick: () =>
|
|
344
|
+
disabled: z.loading,
|
|
345
|
+
onClick: () => G(() => P({ variables: { input: {
|
|
220
346
|
projectId: t,
|
|
221
347
|
name: `${n} board`,
|
|
222
348
|
type: "KANBAN",
|
|
223
349
|
isDefault: !0,
|
|
224
350
|
withDefaultColumns: !0
|
|
225
351
|
} } }), "The board could not be created."),
|
|
226
|
-
children: [/* @__PURE__ */
|
|
352
|
+
children: [/* @__PURE__ */ m(l, { className: "mr-1 h-4 w-4" }), z.loading ? "Creating…" : "Create board"]
|
|
227
353
|
}),
|
|
228
|
-
/* @__PURE__ */
|
|
354
|
+
/* @__PURE__ */ m("p", {
|
|
229
355
|
className: "text-xs text-muted-foreground",
|
|
230
356
|
children: "Starts with one column per status: Backlog, To Do, In Progress, In Review, Testing, Done."
|
|
231
357
|
}),
|
|
232
|
-
|
|
358
|
+
g && /* @__PURE__ */ m(X, { message: g })
|
|
233
359
|
]
|
|
234
360
|
}) });
|
|
235
361
|
}
|
|
236
|
-
function
|
|
237
|
-
let
|
|
362
|
+
function Z({ card: e, column: r, columns: i, busy: a, now: o, onMove: s }) {
|
|
363
|
+
let c = t(), l = K(e.statusSince, o);
|
|
364
|
+
return /* @__PURE__ */ h("li", {
|
|
365
|
+
draggable: !0,
|
|
366
|
+
onDragStart: (t) => {
|
|
367
|
+
t.dataTransfer.setData("text/plain", e.id), t.dataTransfer.effectAllowed = "move";
|
|
368
|
+
},
|
|
369
|
+
className: "rounded-md border bg-background p-2 space-y-1 cursor-grab",
|
|
370
|
+
"data-testid": "board-card",
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ m("button", {
|
|
373
|
+
type: "button",
|
|
374
|
+
className: "text-left text-sm font-medium leading-tight hover:underline",
|
|
375
|
+
onClick: () => c(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
|
|
376
|
+
children: e.title
|
|
377
|
+
}),
|
|
378
|
+
/* @__PURE__ */ h("div", {
|
|
379
|
+
className: "flex items-center justify-between text-[11px] text-muted-foreground",
|
|
380
|
+
children: [
|
|
381
|
+
/* @__PURE__ */ m("span", { children: e.key }),
|
|
382
|
+
l !== null && /* @__PURE__ */ m("span", {
|
|
383
|
+
"data-testid": "card-age",
|
|
384
|
+
className: q(l),
|
|
385
|
+
title: `In ${N(e.status)} since ${new Date(e.statusSince ?? "").toLocaleDateString()}`,
|
|
386
|
+
children: l === 0 ? "today" : `${l}d in status`
|
|
387
|
+
}),
|
|
388
|
+
/* @__PURE__ */ h("span", { children: [e.storyPoints ?? 0, " pts"] })
|
|
389
|
+
]
|
|
390
|
+
}),
|
|
391
|
+
/* @__PURE__ */ h("div", {
|
|
392
|
+
className: "flex items-center justify-between gap-1 text-[11px] text-muted-foreground",
|
|
393
|
+
children: [/* @__PURE__ */ m("span", {
|
|
394
|
+
className: "truncate",
|
|
395
|
+
children: e.assigneeId ? /* @__PURE__ */ m(n, { id: e.assigneeId }) : "Unassigned"
|
|
396
|
+
}), /* @__PURE__ */ h("select", {
|
|
397
|
+
"aria-label": `Move ${e.key} to column`,
|
|
398
|
+
className: "rounded border border-input bg-background px-1 py-0.5 text-[11px]",
|
|
399
|
+
value: "",
|
|
400
|
+
disabled: a,
|
|
401
|
+
onChange: (t) => t.target.value && s(e.id, t.target.value),
|
|
402
|
+
children: [/* @__PURE__ */ m("option", {
|
|
403
|
+
value: "",
|
|
404
|
+
children: "Move…"
|
|
405
|
+
}), i.filter((e) => e.id !== r.id).map((e) => /* @__PURE__ */ m("option", {
|
|
406
|
+
value: e.id,
|
|
407
|
+
children: e.name
|
|
408
|
+
}, e.id))]
|
|
409
|
+
})]
|
|
410
|
+
})
|
|
411
|
+
]
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function Q({ column: e, onMove: t, className: n, children: r, label: a }) {
|
|
415
|
+
let [o, s] = i(!1);
|
|
238
416
|
return /* @__PURE__ */ m("section", {
|
|
239
|
-
"aria-label":
|
|
240
|
-
"
|
|
241
|
-
className: `min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30 ${g ? "ring-2 ring-primary" : ""}`,
|
|
417
|
+
"aria-label": a,
|
|
418
|
+
className: `${n} ${o ? "ring-2 ring-primary" : ""}`,
|
|
242
419
|
onDragOver: (e) => {
|
|
243
|
-
e.preventDefault(),
|
|
420
|
+
e.preventDefault(), s(!0);
|
|
244
421
|
},
|
|
245
|
-
onDragLeave: () =>
|
|
246
|
-
onDrop: (
|
|
247
|
-
|
|
248
|
-
let
|
|
249
|
-
|
|
422
|
+
onDragLeave: () => s(!1),
|
|
423
|
+
onDrop: (n) => {
|
|
424
|
+
n.preventDefault(), s(!1);
|
|
425
|
+
let r = n.dataTransfer.getData("text/plain");
|
|
426
|
+
r && t(r, e.id);
|
|
250
427
|
},
|
|
428
|
+
children: r
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
function $({ cards: e, loading: t, onLoadMore: n }) {
|
|
432
|
+
let r = e.totalCount - e.items.length;
|
|
433
|
+
return r <= 0 || !e.pageInfo.hasNextPage ? null : /* @__PURE__ */ h("div", {
|
|
434
|
+
className: "px-2 pb-2 space-y-1",
|
|
435
|
+
children: [/* @__PURE__ */ h("p", {
|
|
436
|
+
className: "text-xs text-muted-foreground",
|
|
437
|
+
children: [
|
|
438
|
+
"Showing ",
|
|
439
|
+
e.items.length,
|
|
440
|
+
" of ",
|
|
441
|
+
e.totalCount
|
|
442
|
+
]
|
|
443
|
+
}), /* @__PURE__ */ m(w, {
|
|
444
|
+
size: "sm",
|
|
445
|
+
variant: "outline",
|
|
446
|
+
className: "w-full",
|
|
447
|
+
"data-testid": "board-column-more",
|
|
448
|
+
disabled: t,
|
|
449
|
+
onClick: n,
|
|
450
|
+
children: t ? "Loading…" : `Show ${Math.min(r, P)} more`
|
|
451
|
+
})]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function oe({ board: e, laneBy: t, cardsOf: r, now: i, busy: a, loadingMore: o, onLoadMore: s, onMove: c }) {
|
|
455
|
+
let l = e.columns.map((e) => ({
|
|
456
|
+
column: e,
|
|
457
|
+
cards: r(e)
|
|
458
|
+
})), u = [...new Set(l.flatMap(({ cards: e }) => e.items.map((e) => J(e, t))))].sort((e, n) => t === "priority" ? W.indexOf(e) - W.indexOf(n) : e === "" ? 1 : n === "" ? -1 : e.localeCompare(n)), d = (e) => t === "priority" ? N(e) : e ? /* @__PURE__ */ m(n, { id: e }) : "Unassigned";
|
|
459
|
+
return /* @__PURE__ */ h("div", {
|
|
460
|
+
className: "overflow-x-auto pb-2 space-y-3",
|
|
461
|
+
"data-testid": "board-swimlanes",
|
|
251
462
|
children: [
|
|
252
|
-
/* @__PURE__ */
|
|
253
|
-
className: "
|
|
254
|
-
children: [
|
|
255
|
-
|
|
256
|
-
|
|
463
|
+
/* @__PURE__ */ h("p", {
|
|
464
|
+
className: "text-xs text-muted-foreground",
|
|
465
|
+
children: [
|
|
466
|
+
"Dragging moves a card between columns. Its lane follows its",
|
|
467
|
+
" ",
|
|
468
|
+
t === "priority" ? "priority" : "assignee",
|
|
469
|
+
"."
|
|
470
|
+
]
|
|
471
|
+
}),
|
|
472
|
+
/* @__PURE__ */ m("div", {
|
|
473
|
+
className: "flex gap-3",
|
|
474
|
+
children: l.map(({ column: e, cards: t }) => /* @__PURE__ */ m("div", {
|
|
475
|
+
className: "min-w-[240px] w-[240px] shrink-0 px-2",
|
|
476
|
+
children: /* @__PURE__ */ h("h3", {
|
|
257
477
|
className: "text-sm font-medium truncate",
|
|
258
|
-
children: r.name
|
|
259
|
-
}), /* @__PURE__ */ p("span", {
|
|
260
|
-
"data-testid": `board-column-count-${r.name}`,
|
|
261
|
-
className: `text-xs rounded-full px-1.5 ${y ? "bg-status-warning-subtle text-status-warning" : "bg-muted"}`,
|
|
262
|
-
title: r.wipLimit ? `WIP limit ${r.wipLimit}` : void 0,
|
|
263
|
-
children: r.wipLimit ? `${v} / ${r.wipLimit}` : v
|
|
264
|
-
})]
|
|
265
|
-
}), /* @__PURE__ */ m("div", {
|
|
266
|
-
className: "flex items-center justify-between gap-1",
|
|
267
|
-
children: [/* @__PURE__ */ p("span", {
|
|
268
|
-
className: "text-[11px] text-muted-foreground",
|
|
269
|
-
children: r.mappedStatus ? `Sets status: ${P(r.mappedStatus)}` : "No status"
|
|
270
|
-
}), /* @__PURE__ */ m("span", {
|
|
271
|
-
className: "flex",
|
|
272
478
|
children: [
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
className: "
|
|
277
|
-
|
|
278
|
-
disabled: c,
|
|
279
|
-
onClick: () => f(-1),
|
|
280
|
-
children: /* @__PURE__ */ p(a, { className: "h-3.5 w-3.5" })
|
|
281
|
-
}),
|
|
282
|
-
/* @__PURE__ */ p(w, {
|
|
283
|
-
size: "icon",
|
|
284
|
-
variant: "ghost",
|
|
285
|
-
className: "h-6 w-6",
|
|
286
|
-
"aria-label": `Move ${r.name} column right`,
|
|
287
|
-
disabled: l,
|
|
288
|
-
onClick: () => f(1),
|
|
289
|
-
children: /* @__PURE__ */ p(o, { className: "h-3.5 w-3.5" })
|
|
290
|
-
}),
|
|
291
|
-
/* @__PURE__ */ p(Y, {
|
|
292
|
-
boardId: e,
|
|
293
|
-
column: r
|
|
479
|
+
e.name,
|
|
480
|
+
" ",
|
|
481
|
+
/* @__PURE__ */ m("span", {
|
|
482
|
+
className: "text-xs text-muted-foreground",
|
|
483
|
+
children: e.wipLimit ? `${t.totalCount} / ${e.wipLimit}` : t.totalCount
|
|
294
484
|
})
|
|
295
485
|
]
|
|
296
|
-
})
|
|
297
|
-
})
|
|
486
|
+
})
|
|
487
|
+
}, e.id))
|
|
298
488
|
}),
|
|
299
|
-
/* @__PURE__ */ m("
|
|
300
|
-
className: "
|
|
301
|
-
children:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
489
|
+
u.length === 0 && /* @__PURE__ */ m("p", {
|
|
490
|
+
className: "text-sm text-muted-foreground p-4",
|
|
491
|
+
children: "No cards on this board yet."
|
|
492
|
+
}),
|
|
493
|
+
u.map((n) => /* @__PURE__ */ h("div", {
|
|
494
|
+
"data-testid": "board-lane",
|
|
495
|
+
className: "space-y-1",
|
|
496
|
+
children: [/* @__PURE__ */ h("p", {
|
|
497
|
+
className: "text-xs font-medium",
|
|
308
498
|
children: [
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
children:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}),
|
|
319
|
-
/* @__PURE__ */ m("div", {
|
|
320
|
-
className: "flex items-center justify-between gap-1 text-[11px] text-muted-foreground",
|
|
321
|
-
children: [/* @__PURE__ */ p("span", {
|
|
322
|
-
className: "truncate",
|
|
323
|
-
children: e.assigneeId ? /* @__PURE__ */ p(n, { id: e.assigneeId }) : "Unassigned"
|
|
324
|
-
}), /* @__PURE__ */ m("select", {
|
|
325
|
-
"aria-label": `Move ${e.key} to column`,
|
|
326
|
-
className: "rounded border border-input bg-background px-1 py-0.5 text-[11px]",
|
|
327
|
-
value: "",
|
|
328
|
-
disabled: u,
|
|
329
|
-
onChange: (t) => t.target.value && d(e.id, t.target.value),
|
|
330
|
-
children: [/* @__PURE__ */ p("option", {
|
|
331
|
-
value: "",
|
|
332
|
-
children: "Move…"
|
|
333
|
-
}), s.filter((e) => e.id !== r.id).map((e) => /* @__PURE__ */ p("option", {
|
|
334
|
-
value: e.id,
|
|
335
|
-
children: e.name
|
|
336
|
-
}, e.id))]
|
|
337
|
-
})]
|
|
499
|
+
d(n),
|
|
500
|
+
" ",
|
|
501
|
+
/* @__PURE__ */ h("span", {
|
|
502
|
+
className: "text-muted-foreground",
|
|
503
|
+
children: [
|
|
504
|
+
"(",
|
|
505
|
+
l.reduce((e, { cards: r }) => e + r.items.filter((e) => J(e, t) === n).length, 0),
|
|
506
|
+
")"
|
|
507
|
+
]
|
|
338
508
|
})
|
|
339
509
|
]
|
|
340
|
-
}
|
|
341
|
-
className: "
|
|
342
|
-
children:
|
|
510
|
+
}), /* @__PURE__ */ m("div", {
|
|
511
|
+
className: "flex gap-3",
|
|
512
|
+
children: l.map(({ column: r, cards: o }) => /* @__PURE__ */ m(Q, {
|
|
513
|
+
column: r,
|
|
514
|
+
onMove: c,
|
|
515
|
+
label: `${r.name} column`,
|
|
516
|
+
className: "min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30",
|
|
517
|
+
children: /* @__PURE__ */ m("ul", {
|
|
518
|
+
className: "p-2 space-y-2 min-h-[48px]",
|
|
519
|
+
children: o.items.filter((e) => J(e, t) === n).map((t) => /* @__PURE__ */ m(Z, {
|
|
520
|
+
card: t,
|
|
521
|
+
column: r,
|
|
522
|
+
columns: e.columns,
|
|
523
|
+
busy: a,
|
|
524
|
+
now: i,
|
|
525
|
+
onMove: c
|
|
526
|
+
}, t.id))
|
|
527
|
+
})
|
|
528
|
+
}, r.id))
|
|
343
529
|
})]
|
|
344
|
-
}),
|
|
345
|
-
|
|
346
|
-
className: "
|
|
347
|
-
children:
|
|
348
|
-
"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
530
|
+
}, n || "none")),
|
|
531
|
+
/* @__PURE__ */ m("div", {
|
|
532
|
+
className: "flex gap-3",
|
|
533
|
+
children: l.map(({ column: e, cards: t }) => /* @__PURE__ */ m("div", {
|
|
534
|
+
className: "min-w-[240px] w-[240px] shrink-0",
|
|
535
|
+
children: /* @__PURE__ */ m($, {
|
|
536
|
+
cards: t,
|
|
537
|
+
loading: o === e.id,
|
|
538
|
+
onLoadMore: () => s(e)
|
|
539
|
+
})
|
|
540
|
+
}, e.id))
|
|
354
541
|
})
|
|
355
542
|
]
|
|
356
543
|
});
|
|
357
544
|
}
|
|
358
|
-
function
|
|
359
|
-
let
|
|
360
|
-
|
|
545
|
+
function se({ boardId: e, column: t, columns: n, cards: r, isFirst: i, isLast: s, busy: c, now: l, loadingMore: u, onLoadMore: d, onMove: f, onShift: p }) {
|
|
546
|
+
let g = r.totalCount, _ = t.wipLimit != null && t.wipLimit > 0 && g >= t.wipLimit;
|
|
547
|
+
return /* @__PURE__ */ m(Q, {
|
|
548
|
+
column: t,
|
|
549
|
+
onMove: f,
|
|
550
|
+
label: `${t.name} column`,
|
|
551
|
+
className: "min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30",
|
|
552
|
+
children: /* @__PURE__ */ h("div", {
|
|
553
|
+
"data-testid": `board-column-${t.name}`,
|
|
554
|
+
children: [
|
|
555
|
+
/* @__PURE__ */ h("header", {
|
|
556
|
+
className: "p-2 border-b space-y-1",
|
|
557
|
+
children: [/* @__PURE__ */ h("div", {
|
|
558
|
+
className: "flex items-center justify-between gap-1",
|
|
559
|
+
children: [/* @__PURE__ */ m("h3", {
|
|
560
|
+
className: "text-sm font-medium truncate",
|
|
561
|
+
children: t.name
|
|
562
|
+
}), /* @__PURE__ */ m("span", {
|
|
563
|
+
"data-testid": `board-column-count-${t.name}`,
|
|
564
|
+
className: `text-xs rounded-full px-1.5 ${_ ? "bg-status-warning-subtle text-status-warning" : "bg-muted"}`,
|
|
565
|
+
title: t.wipLimit ? `WIP limit ${t.wipLimit}` : void 0,
|
|
566
|
+
children: t.wipLimit ? `${g} / ${t.wipLimit}` : g
|
|
567
|
+
})]
|
|
568
|
+
}), /* @__PURE__ */ h("div", {
|
|
569
|
+
className: "flex items-center justify-between gap-1",
|
|
570
|
+
children: [/* @__PURE__ */ m("span", {
|
|
571
|
+
className: "text-[11px] text-muted-foreground",
|
|
572
|
+
children: t.mappedStatus ? `Sets status: ${N(t.mappedStatus)}` : "No status"
|
|
573
|
+
}), /* @__PURE__ */ h("span", {
|
|
574
|
+
className: "flex",
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ m(w, {
|
|
577
|
+
size: "icon",
|
|
578
|
+
variant: "ghost",
|
|
579
|
+
className: "h-6 w-6",
|
|
580
|
+
"aria-label": `Move ${t.name} column left`,
|
|
581
|
+
disabled: i,
|
|
582
|
+
onClick: () => p(-1),
|
|
583
|
+
children: /* @__PURE__ */ m(a, { className: "h-3.5 w-3.5" })
|
|
584
|
+
}),
|
|
585
|
+
/* @__PURE__ */ m(w, {
|
|
586
|
+
size: "icon",
|
|
587
|
+
variant: "ghost",
|
|
588
|
+
className: "h-6 w-6",
|
|
589
|
+
"aria-label": `Move ${t.name} column right`,
|
|
590
|
+
disabled: s,
|
|
591
|
+
onClick: () => p(1),
|
|
592
|
+
children: /* @__PURE__ */ m(o, { className: "h-3.5 w-3.5" })
|
|
593
|
+
}),
|
|
594
|
+
/* @__PURE__ */ m(ce, {
|
|
595
|
+
boardId: e,
|
|
596
|
+
column: t
|
|
597
|
+
})
|
|
598
|
+
]
|
|
599
|
+
})]
|
|
600
|
+
})]
|
|
601
|
+
}),
|
|
602
|
+
/* @__PURE__ */ h("ul", {
|
|
603
|
+
className: "p-2 space-y-2 min-h-[80px]",
|
|
604
|
+
children: [r.items.map((e) => /* @__PURE__ */ m(Z, {
|
|
605
|
+
card: e,
|
|
606
|
+
column: t,
|
|
607
|
+
columns: n,
|
|
608
|
+
busy: c,
|
|
609
|
+
now: l,
|
|
610
|
+
onMove: f
|
|
611
|
+
}, e.id)), g === 0 && /* @__PURE__ */ m("li", {
|
|
612
|
+
className: "text-xs text-muted-foreground text-center py-4",
|
|
613
|
+
children: "Empty"
|
|
614
|
+
})]
|
|
615
|
+
}),
|
|
616
|
+
/* @__PURE__ */ m($, {
|
|
617
|
+
cards: r,
|
|
618
|
+
loading: u,
|
|
619
|
+
onLoadMore: d
|
|
620
|
+
})
|
|
621
|
+
]
|
|
622
|
+
})
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function ce({ boardId: e, column: t }) {
|
|
626
|
+
let n = t !== void 0, [a, o] = i(!1), [s, u] = i(t?.name ?? ""), [d, p] = i(t?.mappedStatus ?? ""), [T, E] = i(t?.wipLimit ? String(t.wipLimit) : ""), [P, F] = i(null), I = {
|
|
627
|
+
refetchQueries: U,
|
|
361
628
|
awaitRefetchQueries: !0
|
|
362
|
-
}, [
|
|
363
|
-
u(t?.name ?? ""),
|
|
364
|
-
},
|
|
365
|
-
|
|
629
|
+
}, [ie, L] = f(z, I), [R, H] = f(B, I), [W, G] = f(V, I), K = L.loading || H.loading || G.loading, q = T.trim() === "" ? null : Number(T), J = q !== null && (!Number.isInteger(q) || q < 1), ae = s.trim() !== "" && !J && !K, Z = () => {
|
|
630
|
+
u(t?.name ?? ""), p(t?.mappedStatus ?? ""), E(t?.wipLimit ? String(t.wipLimit) : ""), F(null);
|
|
631
|
+
}, Q = async () => {
|
|
632
|
+
F(null);
|
|
366
633
|
let i = {
|
|
367
634
|
name: s.trim(),
|
|
368
|
-
mappedStatus:
|
|
369
|
-
wipLimit:
|
|
635
|
+
mappedStatus: d || null,
|
|
636
|
+
wipLimit: q
|
|
370
637
|
};
|
|
371
638
|
try {
|
|
372
|
-
n ? await r(
|
|
639
|
+
n ? await r(R({ variables: {
|
|
373
640
|
id: t.id,
|
|
374
641
|
input: i
|
|
375
|
-
} })) : await r(
|
|
642
|
+
} })) : await r(ie({ variables: { input: {
|
|
376
643
|
boardId: e,
|
|
377
644
|
type: "CUSTOM",
|
|
378
645
|
...i
|
|
379
646
|
} } })), o(!1);
|
|
380
647
|
} catch (e) {
|
|
381
|
-
|
|
648
|
+
F(Y(e, "The column could not be saved."));
|
|
382
649
|
}
|
|
383
650
|
}, $ = n ? `column-${t.id}` : "new-column";
|
|
384
|
-
return /* @__PURE__ */
|
|
651
|
+
return /* @__PURE__ */ h(te, {
|
|
385
652
|
open: a,
|
|
386
653
|
onOpenChange: (e) => {
|
|
387
|
-
o(e), e &&
|
|
654
|
+
o(e), e && Z();
|
|
388
655
|
},
|
|
389
|
-
children: [/* @__PURE__ */
|
|
656
|
+
children: [/* @__PURE__ */ m(j, {
|
|
390
657
|
asChild: !0,
|
|
391
|
-
children: n ? /* @__PURE__ */
|
|
658
|
+
children: n ? /* @__PURE__ */ m(w, {
|
|
392
659
|
size: "icon",
|
|
393
660
|
variant: "ghost",
|
|
394
661
|
className: "h-6 w-6",
|
|
395
662
|
"aria-label": `Edit ${t.name} column`,
|
|
396
|
-
children: /* @__PURE__ */
|
|
397
|
-
}) : /* @__PURE__ */
|
|
663
|
+
children: /* @__PURE__ */ m(c, { className: "h-3.5 w-3.5" })
|
|
664
|
+
}) : /* @__PURE__ */ h(w, {
|
|
398
665
|
size: "sm",
|
|
399
666
|
variant: "outline",
|
|
400
|
-
children: [/* @__PURE__ */
|
|
667
|
+
children: [/* @__PURE__ */ m(l, { className: "mr-1 h-4 w-4" }), "Add column"]
|
|
401
668
|
})
|
|
402
|
-
}), /* @__PURE__ */
|
|
669
|
+
}), /* @__PURE__ */ h(O, {
|
|
403
670
|
className: "max-w-md",
|
|
404
671
|
children: [
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
/* @__PURE__ */
|
|
672
|
+
/* @__PURE__ */ h(re, { children: [/* @__PURE__ */ m(A, { children: n ? `Edit ${t.name}` : "Add column" }), /* @__PURE__ */ m(k, { children: "Moving a card into a column that sets a status changes the work item's status." })] }),
|
|
673
|
+
/* @__PURE__ */ h("div", {
|
|
407
674
|
className: "space-y-3",
|
|
408
675
|
children: [
|
|
409
|
-
/* @__PURE__ */
|
|
676
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
|
|
410
677
|
htmlFor: `${$}-name`,
|
|
411
678
|
className: "text-sm font-medium",
|
|
412
679
|
children: "Name"
|
|
413
|
-
}), /* @__PURE__ */
|
|
680
|
+
}), /* @__PURE__ */ m(D, {
|
|
414
681
|
id: `${$}-name`,
|
|
415
682
|
className: "mt-1",
|
|
416
683
|
value: s,
|
|
417
684
|
onChange: (e) => u(e.target.value)
|
|
418
685
|
})] }),
|
|
419
|
-
/* @__PURE__ */
|
|
686
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
|
|
420
687
|
htmlFor: `${$}-status`,
|
|
421
688
|
className: "text-sm font-medium",
|
|
422
689
|
children: "Sets status"
|
|
423
|
-
}), /* @__PURE__ */
|
|
690
|
+
}), /* @__PURE__ */ h("select", {
|
|
424
691
|
id: `${$}-status`,
|
|
425
692
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
426
|
-
value:
|
|
427
|
-
onChange: (e) =>
|
|
428
|
-
children: [/* @__PURE__ */
|
|
693
|
+
value: d,
|
|
694
|
+
onChange: (e) => p(e.target.value),
|
|
695
|
+
children: [/* @__PURE__ */ m("option", {
|
|
429
696
|
value: "",
|
|
430
697
|
children: "No status (cards keep theirs)"
|
|
431
|
-
}),
|
|
698
|
+
}), M.map((e) => /* @__PURE__ */ m("option", {
|
|
432
699
|
value: e,
|
|
433
|
-
children:
|
|
700
|
+
children: N(e)
|
|
434
701
|
}, e))]
|
|
435
702
|
})] }),
|
|
436
|
-
/* @__PURE__ */
|
|
437
|
-
/* @__PURE__ */
|
|
703
|
+
/* @__PURE__ */ h("div", { children: [
|
|
704
|
+
/* @__PURE__ */ m("label", {
|
|
438
705
|
htmlFor: `${$}-wip`,
|
|
439
706
|
className: "text-sm font-medium",
|
|
440
707
|
children: "WIP limit (blank for none)"
|
|
441
708
|
}),
|
|
442
|
-
/* @__PURE__ */
|
|
709
|
+
/* @__PURE__ */ m(D, {
|
|
443
710
|
id: `${$}-wip`,
|
|
444
711
|
type: "number",
|
|
445
712
|
min: 1,
|
|
446
713
|
className: "mt-1",
|
|
447
|
-
value:
|
|
448
|
-
onChange: (e) =>
|
|
714
|
+
value: T,
|
|
715
|
+
onChange: (e) => E(e.target.value)
|
|
449
716
|
}),
|
|
450
|
-
|
|
717
|
+
J && /* @__PURE__ */ m(X, { message: "The WIP limit must be a whole number, 1 or more." })
|
|
451
718
|
] })
|
|
452
719
|
]
|
|
453
720
|
}),
|
|
454
|
-
|
|
455
|
-
/* @__PURE__ */
|
|
721
|
+
P && /* @__PURE__ */ m(X, { message: P }),
|
|
722
|
+
/* @__PURE__ */ h(ne, {
|
|
456
723
|
className: "gap-2",
|
|
457
|
-
children: [n && /* @__PURE__ */
|
|
724
|
+
children: [n && /* @__PURE__ */ h(g, { children: [/* @__PURE__ */ m(C, {
|
|
458
725
|
asChild: !0,
|
|
459
|
-
children: /* @__PURE__ */
|
|
726
|
+
children: /* @__PURE__ */ m(w, {
|
|
460
727
|
variant: "outline",
|
|
461
|
-
disabled:
|
|
728
|
+
disabled: K,
|
|
462
729
|
children: "Delete column"
|
|
463
730
|
})
|
|
464
|
-
}), /* @__PURE__ */
|
|
731
|
+
}), /* @__PURE__ */ h(y, { children: [/* @__PURE__ */ h(ee, { children: [/* @__PURE__ */ h(S, { children: [
|
|
465
732
|
"Delete ",
|
|
466
733
|
t.name,
|
|
467
734
|
"?"
|
|
468
|
-
] }), /* @__PURE__ */
|
|
735
|
+
] }), /* @__PURE__ */ m(b, { children: "Its cards are not deleted: each goes back to the column for its status, or off the board if no column sets that status." })] }), /* @__PURE__ */ h(x, { children: [/* @__PURE__ */ m(v, { children: "Keep it" }), /* @__PURE__ */ m(_, {
|
|
469
736
|
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
470
737
|
onClick: async () => {
|
|
471
738
|
try {
|
|
472
|
-
await r(
|
|
739
|
+
await r(W({ variables: { id: t.id } })), o(!1);
|
|
473
740
|
} catch (e) {
|
|
474
|
-
|
|
741
|
+
F(Y(e, "The column could not be deleted."));
|
|
475
742
|
}
|
|
476
743
|
},
|
|
477
744
|
children: "Delete column"
|
|
478
|
-
})] })] })] }), /* @__PURE__ */
|
|
479
|
-
onClick:
|
|
480
|
-
disabled: !
|
|
745
|
+
})] })] })] }), /* @__PURE__ */ m(w, {
|
|
746
|
+
onClick: Q,
|
|
747
|
+
disabled: !ae,
|
|
481
748
|
"data-testid": `${$}-save`,
|
|
482
|
-
children:
|
|
749
|
+
children: K ? "Saving…" : n ? "Save" : "Add column"
|
|
483
750
|
})]
|
|
484
751
|
})
|
|
485
752
|
]
|
|
@@ -487,6 +754,6 @@ function Y({ boardId: e, column: t }) {
|
|
|
487
754
|
});
|
|
488
755
|
}
|
|
489
756
|
//#endregion
|
|
490
|
-
export {
|
|
757
|
+
export { ae as ProjectBoard };
|
|
491
758
|
|
|
492
759
|
//# sourceMappingURL=ProjectBoard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectBoard.js","names":[],"sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"sourcesContent":["import { 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 Button,\n Card,\n CardContent,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight, LayoutGrid, Pencil, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * A project's board (P1.2). The server owns placement: each column's `cards`\n * follow the rule in wspace-planmagnet-svc board.service.ts `columnCardsWhere`\n * (placed column, else the first column mapped to the item's status), and\n * `unplacedCount` says how many items are on no column. Moving a card into a\n * column mapped to a status changes that status on the server, and a column's\n * WIP limit is enforced there too. This component only draws and asks.\n */\n\nconst STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nconst statusLabel = (status: string) =>\n status.charAt(0) + status.slice(1).toLowerCase().replace(/_/g, ' ');\n\nconst CARDS_PER_COLUMN = 100;\n\nconst BOARDS = gql`\n query ProjectBoards($workspaceId: ID!, $projectId: String) {\n planmagnetBoards(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 20 }\n ) {\n items {\n id\n name\n isDefault\n }\n }\n }\n`;\n\nconst BOARD = gql`\n query ProjectBoard($id: ID!) {\n planmagnetBoard(id: $id) {\n id\n name\n unplacedCount\n columns {\n id\n name\n mappedStatus\n wipLimit\n sortOrder\n cards(pagination: { first: 100 }) {\n totalCount\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n }\n }\n }\n }\n }\n`;\n\nconst MOVE_CARD = gql`\n mutation ProjectBoardMoveCard($boardId: ID!, $input: PlanmagnetMoveCardInput!) {\n planmagnetMoveCard(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\nconst CREATE_BOARD = gql`\n mutation ProjectBoardCreate($input: PlanmagnetCreateBoardInput!) {\n planmagnetCreateBoard(input: $input) {\n id\n }\n }\n`;\nconst CREATE_COLUMN = gql`\n mutation ProjectBoardCreateColumn($input: PlanmagnetCreateBoardColumnInput!) {\n planmagnetCreateBoardColumn(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_COLUMN = gql`\n mutation ProjectBoardUpdateColumn($id: ID!, $input: PlanmagnetUpdateBoardColumnInput!) {\n planmagnetUpdateBoardColumn(id: $id, input: $input) {\n id\n }\n }\n`;\nconst DELETE_COLUMN = gql`\n mutation ProjectBoardDeleteColumn($id: ID!) {\n planmagnetDeleteBoardColumn(id: $id)\n }\n`;\nconst REORDER_COLUMNS = gql`\n mutation ProjectBoardReorder($boardId: ID!, $input: PlanmagnetReorderColumnsInput!) {\n planmagnetReorderColumns(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\n\n/** Every query whose numbers a board change can move. */\nconst REFETCH = [\n 'ProjectBoard',\n 'ProjectBoards',\n 'GetWorkItems',\n 'GetWorkItemCounts',\n 'GetDashboardData',\n];\n\ninterface BoardCard {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n}\n\ninterface BoardColumn {\n id: string;\n name: string;\n mappedStatus: string | null;\n wipLimit: number | null;\n sortOrder: number;\n cards: { totalCount: number; items: BoardCard[] };\n}\n\ninterface BoardData {\n id: string;\n name: string;\n unplacedCount: number;\n columns: BoardColumn[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\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 ProjectBoard({\n projectId,\n projectName,\n}: {\n projectId: string;\n projectName: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const [chosenBoardId, setChosenBoardId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const { data: boardsData, loading: boardsLoading } = useQuery<{\n planmagnetBoards: { items: Array<{ id: string; name: string; isDefault: boolean }> };\n }>(BOARDS, { variables: { workspaceId, projectId }, skip: !workspaceId });\n const boards = boardsData?.planmagnetBoards.items ?? [];\n const boardId =\n (chosenBoardId && boards.some((b) => b.id === chosenBoardId) ? chosenBoardId : null) ??\n boards.find((b) => b.isDefault)?.id ??\n boards[0]?.id ??\n null;\n\n const { data, loading } = useQuery<{ planmagnetBoard: BoardData | null }>(BOARD, {\n variables: { id: boardId },\n skip: !boardId,\n });\n const board = data?.planmagnetBoard ?? null;\n\n const [createBoard, createBoardState] = useMutation<{ planmagnetCreateBoard: { id: string } }>(\n CREATE_BOARD,\n { refetchQueries: REFETCH, awaitRefetchQueries: true }\n );\n const [moveCard, moveState] = useMutation(MOVE_CARD, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n const [reorder] = useMutation(REORDER_COLUMNS, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(messageOf(e, fallback));\n }\n };\n\n const move = (workItemId: string, targetColumnId: string) =>\n run(\n () => moveCard({ variables: { boardId, input: { workItemId, targetColumnId } } }),\n 'The card could not be moved.'\n );\n\n const shiftColumn = (index: number, delta: -1 | 1) => {\n if (!board) return;\n const order = board.columns.map((c) => c.id);\n const target = index + delta;\n if (target < 0 || target >= order.length) return;\n [order[index], order[target]] = [order[target], order[index]];\n return run(\n () => reorder({ variables: { boardId, input: { columnIds: order } } }),\n 'The columns could not be reordered.'\n );\n };\n\n if (boardsLoading && !boardsData) {\n return <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>;\n }\n\n if (!boardId) {\n return (\n <Card>\n <CardContent className=\"p-8 text-center space-y-3\" data-testid=\"board-empty\">\n <LayoutGrid className=\"h-8 w-8 mx-auto text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">{projectName} has no board yet.</p>\n <Button\n size=\"sm\"\n disabled={createBoardState.loading}\n onClick={() =>\n run(\n () =>\n createBoard({\n variables: {\n input: {\n projectId,\n name: `${projectName} board`,\n type: 'KANBAN',\n isDefault: true,\n withDefaultColumns: true,\n },\n },\n }),\n 'The board could not be created.'\n )\n }\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n {createBoardState.loading ? 'Creating…' : 'Create board'}\n </Button>\n <p className=\"text-xs text-muted-foreground\">\n Starts with one column per status: Backlog, To Do, In Progress, In Review, Testing,\n Done.\n </p>\n {error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n }\n\n return (\n <div className=\"space-y-3\" data-testid=\"project-board\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {boards.length > 1 && (\n <select\n aria-label=\"Board\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={boardId}\n onChange={(e) => setChosenBoardId(e.target.value)}\n >\n {boards.map((b) => (\n <option key={b.id} value={b.id}>\n {b.name}\n </option>\n ))}\n </select>\n )}\n {board && board.unplacedCount > 0 && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"board-unplaced\">\n {board.unplacedCount} work item{board.unplacedCount === 1 ? ' is' : 's are'} not on this\n board: {board.unplacedCount === 1 ? 'its status has' : 'their status has'} no column.\n </p>\n )}\n {board && (\n <div className=\"ml-auto\">\n <ColumnDialog boardId={board.id} />\n </div>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n {loading && !board ? (\n <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {board?.columns.map((column, index) => (\n <BoardColumnView\n key={column.id}\n boardId={board.id}\n column={column}\n columns={board.columns}\n isFirst={index === 0}\n isLast={index === board.columns.length - 1}\n busy={moveState.loading}\n onMove={move}\n onShift={(delta) => shiftColumn(index, delta)}\n />\n ))}\n {board && board.columns.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">\n This board has no columns. Add one to start placing work.\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction BoardColumnView({\n boardId,\n column,\n columns,\n isFirst,\n isLast,\n busy,\n onMove,\n onShift,\n}: {\n boardId: string;\n column: BoardColumn;\n columns: BoardColumn[];\n isFirst: boolean;\n isLast: boolean;\n busy: boolean;\n onMove: (workItemId: string, targetColumnId: string) => void;\n onShift: (delta: -1 | 1) => void;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [dragOver, setDragOver] = useState(false);\n const count = column.cards.totalCount;\n const atLimit = column.wipLimit != null && column.wipLimit > 0 && count >= column.wipLimit;\n const hidden = count - column.cards.items.length;\n\n return (\n <section\n aria-label={`${column.name} column`}\n data-testid={`board-column-${column.name}`}\n className={`min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30 ${dragOver ? 'ring-2 ring-primary' : ''}`}\n onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n const workItemId = e.dataTransfer.getData('text/plain');\n if (workItemId) onMove(workItemId, column.id);\n }}\n >\n <header className=\"p-2 border-b space-y-1\">\n <div className=\"flex items-center justify-between gap-1\">\n <h3 className=\"text-sm font-medium truncate\">{column.name}</h3>\n <span\n data-testid={`board-column-count-${column.name}`}\n className={`text-xs rounded-full px-1.5 ${atLimit ? 'bg-status-warning-subtle text-status-warning' : 'bg-muted'}`}\n title={column.wipLimit ? `WIP limit ${column.wipLimit}` : undefined}\n >\n {column.wipLimit ? `${count} / ${column.wipLimit}` : count}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-1\">\n <span className=\"text-[11px] text-muted-foreground\">\n {column.mappedStatus ? `Sets status: ${statusLabel(column.mappedStatus)}` : 'No status'}\n </span>\n <span className=\"flex\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column left`}\n disabled={isFirst}\n onClick={() => onShift(-1)}\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column right`}\n disabled={isLast}\n onClick={() => onShift(1)}\n >\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </Button>\n <ColumnDialog boardId={boardId} column={column} />\n </span>\n </div>\n </header>\n <ul className=\"p-2 space-y-2 min-h-[80px]\">\n {column.cards.items.map((card) => (\n <li\n key={card.id}\n draggable\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className=\"rounded-md border bg-background p-2 space-y-1 cursor-grab\"\n data-testid=\"board-card\"\n >\n <button\n type=\"button\"\n className=\"text-left text-sm font-medium leading-tight hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(card.id)}`)\n }\n >\n {card.title}\n </button>\n <div className=\"flex items-center justify-between text-[11px] text-muted-foreground\">\n <span>{card.key}</span>\n <span>{card.storyPoints ?? 0} pts</span>\n </div>\n <div className=\"flex items-center justify-between gap-1 text-[11px] text-muted-foreground\">\n <span className=\"truncate\">\n {card.assigneeId ? <PersonName id={card.assigneeId} /> : 'Unassigned'}\n </span>\n {/* The same move as drag-and-drop, for keyboard and assistive tech. */}\n <select\n aria-label={`Move ${card.key} to column`}\n className=\"rounded border border-input bg-background px-1 py-0.5 text-[11px]\"\n value=\"\"\n disabled={busy}\n onChange={(e) => e.target.value && onMove(card.id, e.target.value)}\n >\n <option value=\"\">Move…</option>\n {columns\n .filter((c) => c.id !== column.id)\n .map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n </div>\n </li>\n ))}\n {count === 0 && <li className=\"text-xs text-muted-foreground text-center py-4\">Empty</li>}\n </ul>\n {hidden > 0 && (\n <p className=\"px-2 pb-2 text-xs text-muted-foreground\">\n +{hidden} more (showing the first {CARDS_PER_COLUMN})\n </p>\n )}\n </section>\n );\n}\n\n/** Add a column (no `column`) or edit one: name, the status it sets, WIP limit; delete. */\nfunction ColumnDialog({ boardId, column }: { boardId: string; column?: BoardColumn }) {\n const editing = column !== undefined;\n const [open, setOpen] = useState(false);\n const [name, setName] = useState(column?.name ?? '');\n const [mappedStatus, setMappedStatus] = useState(column?.mappedStatus ?? '');\n const [wip, setWip] = useState(column?.wipLimit ? String(column.wipLimit) : '');\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: REFETCH, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_COLUMN, options);\n const [update, updateState] = useMutation(UPDATE_COLUMN, options);\n const [remove, removeState] = useMutation(DELETE_COLUMN, options);\n const busy = createState.loading || updateState.loading || removeState.loading;\n\n const wipValue = wip.trim() === '' ? null : Number(wip);\n const invalidWip = wipValue !== null && (!Number.isInteger(wipValue) || wipValue < 1);\n const canSave = name.trim() !== '' && !invalidWip && !busy;\n\n const reset = () => {\n setName(column?.name ?? '');\n setMappedStatus(column?.mappedStatus ?? '');\n setWip(column?.wipLimit ? String(column.wipLimit) : '');\n setError(null);\n };\n\n const save = async () => {\n setError(null);\n const fields = {\n name: name.trim(),\n mappedStatus: mappedStatus || null,\n wipLimit: wipValue,\n };\n try {\n if (editing) {\n await throwOnError(update({ variables: { id: column.id, input: fields } }));\n } else {\n await throwOnError(\n create({ variables: { input: { boardId, type: 'CUSTOM', ...fields } } })\n );\n }\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be saved.'));\n }\n };\n\n const idPrefix = editing ? `column-${column.id}` : 'new-column';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {editing ? (\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Edit ${column.name} column`}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add column\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? `Edit ${column.name}` : 'Add column'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Moving a card into a column that sets a status changes the work item's status.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-name`} className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id={`${idPrefix}-name`}\n className=\"mt-1\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Sets status\n </label>\n <select\n id={`${idPrefix}-status`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={mappedStatus}\n onChange={(e) => setMappedStatus(e.target.value)}\n >\n <option value=\"\">No status (cards keep theirs)</option>\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {statusLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-wip`} className=\"text-sm font-medium\">\n WIP limit (blank for none)\n </label>\n <Input\n id={`${idPrefix}-wip`}\n type=\"number\"\n min={1}\n className=\"mt-1\"\n value={wip}\n onChange={(e) => setWip(e.target.value)}\n />\n {invalidWip && <ErrorLine message=\"The WIP limit must be a whole number, 1 or more.\" />}\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter className=\"gap-2\">\n {editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\" disabled={busy}>\n Delete column\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete {column.name}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its cards are not deleted: each goes back to the column for its status, or off\n the board if no column sets that status.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n try {\n await throwOnError(remove({ variables: { id: column.id } }));\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be deleted.'));\n }\n }}\n >\n Delete column\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n <Button onClick={save} disabled={!canSave} data-testid={`${idPrefix}-save`}>\n {busy ? 'Saving…' : editing ? 'Save' : 'Add column'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectBoard.js","names":[],"sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"sourcesContent":["import { useState, type ReactNode } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, 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 Button,\n Card,\n CardContent,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight, LayoutGrid, Pencil, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * A project's board (P1.2). The server owns placement: each column's `cards`\n * follow the rule in wspace-planmagnet-svc board.service.ts `columnCardsWhere`\n * (placed column, else the first column mapped to the item's status), and\n * `unplacedCount` says how many items are on no column. Moving a card into a\n * column mapped to a status changes that status on the server, and a column's\n * WIP limit is enforced there too. This component only draws and asks.\n */\n\nconst STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nconst statusLabel = (status: string) =>\n status.charAt(0) + status.slice(1).toLowerCase().replace(/_/g, ' ');\n\nconst CARDS_PER_COLUMN = 100;\n\nconst BOARDS = gql`\n query ProjectBoards($workspaceId: ID!, $projectId: String) {\n planmagnetBoards(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 20 }\n ) {\n items {\n id\n name\n isDefault\n }\n }\n }\n`;\n\n/** The next page of one column (P1.2: a column shows 100 cards and used to say only \"+N more\"). */\nconst COLUMN_PAGE = gql`\n query ProjectBoardColumnPage($boardId: ID!, $columnId: ID!, $after: String) {\n planmagnetBoardColumnCards(\n boardId: $boardId\n columnId: $columnId\n pagination: { first: 100, after: $after }\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n`;\n\nconst BOARD = gql`\n query ProjectBoard($id: ID!) {\n planmagnetBoard(id: $id) {\n id\n name\n unplacedCount\n columns {\n id\n name\n mappedStatus\n wipLimit\n sortOrder\n cards(pagination: { first: 100 }) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n }\n }\n`;\n\nconst MOVE_CARD = gql`\n mutation ProjectBoardMoveCard($boardId: ID!, $input: PlanmagnetMoveCardInput!) {\n planmagnetMoveCard(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\nconst CREATE_BOARD = gql`\n mutation ProjectBoardCreate($input: PlanmagnetCreateBoardInput!) {\n planmagnetCreateBoard(input: $input) {\n id\n }\n }\n`;\nconst CREATE_COLUMN = gql`\n mutation ProjectBoardCreateColumn($input: PlanmagnetCreateBoardColumnInput!) {\n planmagnetCreateBoardColumn(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_COLUMN = gql`\n mutation ProjectBoardUpdateColumn($id: ID!, $input: PlanmagnetUpdateBoardColumnInput!) {\n planmagnetUpdateBoardColumn(id: $id, input: $input) {\n id\n }\n }\n`;\nconst DELETE_COLUMN = gql`\n mutation ProjectBoardDeleteColumn($id: ID!) {\n planmagnetDeleteBoardColumn(id: $id)\n }\n`;\nconst REORDER_COLUMNS = gql`\n mutation ProjectBoardReorder($boardId: ID!, $input: PlanmagnetReorderColumnsInput!) {\n planmagnetReorderColumns(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\n\n/** Every query whose numbers a board change can move. */\nconst REFETCH = [\n 'ProjectBoard',\n 'ProjectBoards',\n 'GetWorkItems',\n 'GetWorkItemCounts',\n 'GetDashboardData',\n];\n\ninterface BoardCard {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n /** When it entered its current status; null when unknown (changed by a path that records no history). */\n statusSince: string | null;\n}\n\ninterface PageInfo {\n hasNextPage: boolean;\n endCursor: string | null;\n}\n\ninterface BoardColumn {\n id: string;\n name: string;\n mappedStatus: string | null;\n wipLimit: number | null;\n sortOrder: number;\n cards: { totalCount: number; pageInfo: PageInfo; items: BoardCard[] };\n}\n\ntype LaneBy = 'none' | 'assignee' | 'priority';\nconst PRIORITY_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NONE'];\nconst DAY_MS = 86_400_000;\n\n/** Whole days in the current status, or null when unknown. */\nexport function daysInStatus(statusSince: string | null, now: number): number | null {\n if (!statusSince) return null;\n return Math.max(0, Math.floor((now - new Date(statusSince).getTime()) / DAY_MS));\n}\n\n/** Card aging: quiet under a week, a warning from 7 days, stronger from 14. Semantic tokens only. */\nfunction ageClass(days: number): string {\n if (days >= 14) return 'text-destructive font-medium';\n if (days >= 7) return 'text-status-warning-text';\n return 'text-muted-foreground';\n}\n\nfunction laneOf(card: BoardCard, by: LaneBy): string {\n if (by === 'assignee') return card.assigneeId ?? '';\n if (by === 'priority') return card.priority || 'NONE';\n return '';\n}\n\ninterface BoardData {\n id: string;\n name: string;\n unplacedCount: number;\n columns: BoardColumn[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\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 ProjectBoard({\n projectId,\n projectName,\n}: {\n projectId: string;\n projectName: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n const [chosenBoardId, setChosenBoardId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n // Card age is read against the moment the board opened (Date.now() may not run during render).\n const [now] = useState(() => Date.now());\n const [laneBy, setLaneBy] = useState<LaneBy>('none');\n // Pages loaded past each column's first 100, keyed by the board data they extend: a refetch starts over.\n const [more, setMore] = useState<{\n key: unknown;\n byColumn: Record<string, { items: BoardCard[]; pageInfo: PageInfo }>;\n }>({\n key: null,\n byColumn: {},\n });\n const [loadingMore, setLoadingMore] = useState<string | null>(null);\n\n const { data: boardsData, loading: boardsLoading } = useQuery<{\n planmagnetBoards: { items: Array<{ id: string; name: string; isDefault: boolean }> };\n }>(BOARDS, { variables: { workspaceId, projectId }, skip: !workspaceId });\n const boards = boardsData?.planmagnetBoards.items ?? [];\n const boardId =\n (chosenBoardId && boards.some((b) => b.id === chosenBoardId) ? chosenBoardId : null) ??\n boards.find((b) => b.isDefault)?.id ??\n boards[0]?.id ??\n null;\n\n const { data, loading } = useQuery<{ planmagnetBoard: BoardData | null }>(BOARD, {\n variables: { id: boardId },\n skip: !boardId,\n });\n const board = data?.planmagnetBoard ?? null;\n const extra = more.key === board ? more.byColumn : {};\n /** A column with every page loaded so far. */\n const cardsOf = (column: BoardColumn) => {\n const loaded = extra[column.id];\n return {\n items: loaded ? [...column.cards.items, ...loaded.items] : column.cards.items,\n pageInfo: loaded ? loaded.pageInfo : column.cards.pageInfo,\n totalCount: column.cards.totalCount,\n };\n };\n const loadMore = async (column: BoardColumn) => {\n const cursor = cardsOf(column).pageInfo.endCursor;\n if (!board || !cursor) return;\n setError(null);\n setLoadingMore(column.id);\n try {\n const res = await client.query<{\n planmagnetBoardColumnCards: { pageInfo: PageInfo; items: BoardCard[] };\n }>({\n query: COLUMN_PAGE,\n variables: { boardId: board.id, columnId: column.id, after: cursor },\n fetchPolicy: 'network-only',\n });\n const page = res.data?.planmagnetBoardColumnCards;\n if (!page) throw new Error(res.error?.message ?? 'The next cards could not be loaded.');\n const prior = extra[column.id]?.items ?? [];\n setMore({\n key: board,\n byColumn: {\n ...extra,\n [column.id]: { items: [...prior, ...page.items], pageInfo: page.pageInfo },\n },\n });\n } catch (e) {\n setError(messageOf(e, 'The next cards could not be loaded.'));\n } finally {\n setLoadingMore(null);\n }\n };\n\n const [createBoard, createBoardState] = useMutation<{ planmagnetCreateBoard: { id: string } }>(\n CREATE_BOARD,\n { refetchQueries: REFETCH, awaitRefetchQueries: true }\n );\n const [moveCard, moveState] = useMutation(MOVE_CARD, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n const [reorder] = useMutation(REORDER_COLUMNS, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(messageOf(e, fallback));\n }\n };\n\n const move = (workItemId: string, targetColumnId: string) =>\n run(\n () => moveCard({ variables: { boardId, input: { workItemId, targetColumnId } } }),\n 'The card could not be moved.'\n );\n\n const shiftColumn = (index: number, delta: -1 | 1) => {\n if (!board) return;\n const order = board.columns.map((c) => c.id);\n const target = index + delta;\n if (target < 0 || target >= order.length) return;\n [order[index], order[target]] = [order[target], order[index]];\n return run(\n () => reorder({ variables: { boardId, input: { columnIds: order } } }),\n 'The columns could not be reordered.'\n );\n };\n\n if (boardsLoading && !boardsData) {\n return <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>;\n }\n\n if (!boardId) {\n return (\n <Card>\n <CardContent className=\"p-8 text-center space-y-3\" data-testid=\"board-empty\">\n <LayoutGrid className=\"h-8 w-8 mx-auto text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">{projectName} has no board yet.</p>\n <Button\n size=\"sm\"\n disabled={createBoardState.loading}\n onClick={() =>\n run(\n () =>\n createBoard({\n variables: {\n input: {\n projectId,\n name: `${projectName} board`,\n type: 'KANBAN',\n isDefault: true,\n withDefaultColumns: true,\n },\n },\n }),\n 'The board could not be created.'\n )\n }\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n {createBoardState.loading ? 'Creating…' : 'Create board'}\n </Button>\n <p className=\"text-xs text-muted-foreground\">\n Starts with one column per status: Backlog, To Do, In Progress, In Review, Testing,\n Done.\n </p>\n {error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n }\n\n return (\n <div className=\"space-y-3\" data-testid=\"project-board\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {boards.length > 1 && (\n <select\n aria-label=\"Board\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={boardId}\n onChange={(e) => setChosenBoardId(e.target.value)}\n >\n {boards.map((b) => (\n <option key={b.id} value={b.id}>\n {b.name}\n </option>\n ))}\n </select>\n )}\n {board && (\n <label className=\"flex items-center gap-1 text-sm\">\n <span className=\"text-muted-foreground\">Swimlanes</span>\n <select\n aria-label=\"Swimlanes\"\n data-testid=\"board-lanes\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={laneBy}\n onChange={(e) => setLaneBy(e.target.value as LaneBy)}\n >\n <option value=\"none\">None</option>\n <option value=\"assignee\">By assignee</option>\n <option value=\"priority\">By priority</option>\n </select>\n </label>\n )}\n {board && board.unplacedCount > 0 && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"board-unplaced\">\n {board.unplacedCount} work item{board.unplacedCount === 1 ? ' is' : 's are'} not on this\n board: {board.unplacedCount === 1 ? 'its status has' : 'their status has'} no column.\n </p>\n )}\n {board && (\n <div className=\"ml-auto\">\n <ColumnDialog boardId={board.id} />\n </div>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n {loading && !board ? (\n <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>\n ) : board && laneBy !== 'none' ? (\n <SwimlaneBoard\n board={board}\n laneBy={laneBy}\n cardsOf={cardsOf}\n now={now}\n busy={moveState.loading}\n loadingMore={loadingMore}\n onLoadMore={(column) => void loadMore(column)}\n onMove={move}\n />\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {board?.columns.map((column, index) => (\n <BoardColumnView\n key={column.id}\n boardId={board.id}\n column={column}\n columns={board.columns}\n cards={cardsOf(column)}\n isFirst={index === 0}\n isLast={index === board.columns.length - 1}\n busy={moveState.loading}\n now={now}\n loadingMore={loadingMore === column.id}\n onLoadMore={() => void loadMore(column)}\n onMove={move}\n onShift={(delta) => shiftColumn(index, delta)}\n />\n ))}\n {board && board.columns.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">\n This board has no columns. Add one to start placing work.\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n\ntype ColumnCards = { items: BoardCard[]; pageInfo: PageInfo; totalCount: number };\n\n/** One card: title, key, points, assignee, age in status, and a keyboard \"Move…\" equal to drag-and-drop. */\nfunction CardView({\n card,\n column,\n columns,\n busy,\n now,\n onMove,\n}: {\n card: BoardCard;\n column: BoardColumn;\n columns: BoardColumn[];\n busy: boolean;\n now: number;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const navigate = usePlanMagnetNavigate();\n const days = daysInStatus(card.statusSince, now);\n return (\n <li\n draggable\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className=\"rounded-md border bg-background p-2 space-y-1 cursor-grab\"\n data-testid=\"board-card\"\n >\n <button\n type=\"button\"\n className=\"text-left text-sm font-medium leading-tight hover:underline\"\n onClick={() => navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(card.id)}`)}\n >\n {card.title}\n </button>\n <div className=\"flex items-center justify-between text-[11px] text-muted-foreground\">\n <span>{card.key}</span>\n {/* Card aging (P1.2): how long it has had this status. Unknown when its status changed off the record. */}\n {days !== null && (\n <span\n data-testid=\"card-age\"\n className={ageClass(days)}\n title={`In ${statusLabel(card.status)} since ${new Date(card.statusSince ?? '').toLocaleDateString()}`}\n >\n {days === 0 ? 'today' : `${days}d in status`}\n </span>\n )}\n <span>{card.storyPoints ?? 0} pts</span>\n </div>\n <div className=\"flex items-center justify-between gap-1 text-[11px] text-muted-foreground\">\n <span className=\"truncate\">\n {card.assigneeId ? <PersonName id={card.assigneeId} /> : 'Unassigned'}\n </span>\n {/* The same move as drag-and-drop, for keyboard and assistive tech. */}\n <select\n aria-label={`Move ${card.key} to column`}\n className=\"rounded border border-input bg-background px-1 py-0.5 text-[11px]\"\n value=\"\"\n disabled={busy}\n onChange={(e) => e.target.value && onMove(card.id, e.target.value)}\n >\n <option value=\"\">Move…</option>\n {columns\n .filter((c) => c.id !== column.id)\n .map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n </div>\n </li>\n );\n}\n\n/** A drop target: dropping a card here moves it into `column`. */\nfunction DropZone({\n column,\n onMove,\n className,\n children,\n label,\n}: {\n column: BoardColumn;\n onMove: (workItemId: string, targetColumnId: string) => void;\n className: string;\n children: ReactNode;\n label: string;\n}) {\n const [dragOver, setDragOver] = useState(false);\n return (\n <section\n aria-label={label}\n className={`${className} ${dragOver ? 'ring-2 ring-primary' : ''}`}\n onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n const workItemId = e.dataTransfer.getData('text/plain');\n if (workItemId) onMove(workItemId, column.id);\n }}\n >\n {children}\n </section>\n );\n}\n\n/** \"Showing N of M — Show more\" for a column with cards past the ones loaded. */\nfunction MoreCards({\n cards,\n loading,\n onLoadMore,\n}: {\n cards: ColumnCards;\n loading: boolean;\n onLoadMore: () => void;\n}) {\n const hidden = cards.totalCount - cards.items.length;\n if (hidden <= 0 || !cards.pageInfo.hasNextPage) return null;\n return (\n <div className=\"px-2 pb-2 space-y-1\">\n <p className=\"text-xs text-muted-foreground\">\n Showing {cards.items.length} of {cards.totalCount}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"w-full\"\n data-testid=\"board-column-more\"\n disabled={loading}\n onClick={onLoadMore}\n >\n {loading ? 'Loading…' : `Show ${Math.min(hidden, CARDS_PER_COLUMN)} more`}\n </Button>\n </div>\n );\n}\n\n/**\n * Swimlanes (P1.2): rows by assignee or priority across the same columns. Moving a card changes its column (and the\n * status that column sets); its lane follows its assignee or priority, which are changed on the card itself.\n */\nfunction SwimlaneBoard({\n board,\n laneBy,\n cardsOf,\n now,\n busy,\n loadingMore,\n onLoadMore,\n onMove,\n}: {\n board: BoardData;\n laneBy: LaneBy;\n cardsOf: (column: BoardColumn) => ColumnCards;\n now: number;\n busy: boolean;\n loadingMore: string | null;\n onLoadMore: (column: BoardColumn) => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const perColumn = board.columns.map((column) => ({ column, cards: cardsOf(column) }));\n const lanes = [\n ...new Set(perColumn.flatMap(({ cards }) => cards.items.map((c) => laneOf(c, laneBy)))),\n ].sort((a, b) =>\n laneBy === 'priority'\n ? PRIORITY_ORDER.indexOf(a) - PRIORITY_ORDER.indexOf(b)\n : a === ''\n ? 1\n : b === ''\n ? -1\n : a.localeCompare(b)\n );\n const laneName = (lane: string) =>\n laneBy === 'priority' ? statusLabel(lane) : lane ? <PersonName id={lane} /> : 'Unassigned';\n return (\n <div className=\"overflow-x-auto pb-2 space-y-3\" data-testid=\"board-swimlanes\">\n <p className=\"text-xs text-muted-foreground\">\n Dragging moves a card between columns. Its lane follows its{' '}\n {laneBy === 'priority' ? 'priority' : 'assignee'}.\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0 px-2\">\n <h3 className=\"text-sm font-medium truncate\">\n {column.name}{' '}\n <span className=\"text-xs text-muted-foreground\">\n {column.wipLimit ? `${cards.totalCount} / ${column.wipLimit}` : cards.totalCount}\n </span>\n </h3>\n </div>\n ))}\n </div>\n {lanes.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">No cards on this board yet.</p>\n )}\n {lanes.map((lane) => (\n <div key={lane || 'none'} data-testid=\"board-lane\" className=\"space-y-1\">\n <p className=\"text-xs font-medium\">\n {laneName(lane)}{' '}\n <span className=\"text-muted-foreground\">\n (\n {perColumn.reduce(\n (n, { cards }) => n + cards.items.filter((c) => laneOf(c, laneBy) === lane).length,\n 0\n )}\n )\n </span>\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <DropZone\n key={column.id}\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <ul className=\"p-2 space-y-2 min-h-[48px]\">\n {cards.items\n .filter((c) => laneOf(c, laneBy) === lane)\n .map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={board.columns}\n busy={busy}\n now={now}\n onMove={onMove}\n />\n ))}\n </ul>\n </DropZone>\n ))}\n </div>\n </div>\n ))}\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0\">\n <MoreCards\n cards={cards}\n loading={loadingMore === column.id}\n onLoadMore={() => onLoadMore(column)}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nfunction BoardColumnView({\n boardId,\n column,\n columns,\n cards,\n isFirst,\n isLast,\n busy,\n now,\n loadingMore,\n onLoadMore,\n onMove,\n onShift,\n}: {\n boardId: string;\n column: BoardColumn;\n columns: BoardColumn[];\n cards: ColumnCards;\n isFirst: boolean;\n isLast: boolean;\n busy: boolean;\n now: number;\n loadingMore: boolean;\n onLoadMore: () => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n onShift: (delta: -1 | 1) => void;\n}) {\n const count = cards.totalCount;\n const atLimit = column.wipLimit != null && column.wipLimit > 0 && count >= column.wipLimit;\n\n return (\n <DropZone\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <div data-testid={`board-column-${column.name}`}>\n <header className=\"p-2 border-b space-y-1\">\n <div className=\"flex items-center justify-between gap-1\">\n <h3 className=\"text-sm font-medium truncate\">{column.name}</h3>\n <span\n data-testid={`board-column-count-${column.name}`}\n className={`text-xs rounded-full px-1.5 ${atLimit ? 'bg-status-warning-subtle text-status-warning' : 'bg-muted'}`}\n title={column.wipLimit ? `WIP limit ${column.wipLimit}` : undefined}\n >\n {column.wipLimit ? `${count} / ${column.wipLimit}` : count}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-1\">\n <span className=\"text-[11px] text-muted-foreground\">\n {column.mappedStatus\n ? `Sets status: ${statusLabel(column.mappedStatus)}`\n : 'No status'}\n </span>\n <span className=\"flex\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column left`}\n disabled={isFirst}\n onClick={() => onShift(-1)}\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column right`}\n disabled={isLast}\n onClick={() => onShift(1)}\n >\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </Button>\n <ColumnDialog boardId={boardId} column={column} />\n </span>\n </div>\n </header>\n <ul className=\"p-2 space-y-2 min-h-[80px]\">\n {cards.items.map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={columns}\n busy={busy}\n now={now}\n onMove={onMove}\n />\n ))}\n {count === 0 && <li className=\"text-xs text-muted-foreground text-center py-4\">Empty</li>}\n </ul>\n <MoreCards cards={cards} loading={loadingMore} onLoadMore={onLoadMore} />\n </div>\n </DropZone>\n );\n}\n\nfunction ColumnDialog({ boardId, column }: { boardId: string; column?: BoardColumn }) {\n const editing = column !== undefined;\n const [open, setOpen] = useState(false);\n const [name, setName] = useState(column?.name ?? '');\n const [mappedStatus, setMappedStatus] = useState(column?.mappedStatus ?? '');\n const [wip, setWip] = useState(column?.wipLimit ? String(column.wipLimit) : '');\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: REFETCH, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_COLUMN, options);\n const [update, updateState] = useMutation(UPDATE_COLUMN, options);\n const [remove, removeState] = useMutation(DELETE_COLUMN, options);\n const busy = createState.loading || updateState.loading || removeState.loading;\n\n const wipValue = wip.trim() === '' ? null : Number(wip);\n const invalidWip = wipValue !== null && (!Number.isInteger(wipValue) || wipValue < 1);\n const canSave = name.trim() !== '' && !invalidWip && !busy;\n\n const reset = () => {\n setName(column?.name ?? '');\n setMappedStatus(column?.mappedStatus ?? '');\n setWip(column?.wipLimit ? String(column.wipLimit) : '');\n setError(null);\n };\n\n const save = async () => {\n setError(null);\n const fields = {\n name: name.trim(),\n mappedStatus: mappedStatus || null,\n wipLimit: wipValue,\n };\n try {\n if (editing) {\n await throwOnError(update({ variables: { id: column.id, input: fields } }));\n } else {\n await throwOnError(\n create({ variables: { input: { boardId, type: 'CUSTOM', ...fields } } })\n );\n }\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be saved.'));\n }\n };\n\n const idPrefix = editing ? `column-${column.id}` : 'new-column';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {editing ? (\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Edit ${column.name} column`}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add column\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? `Edit ${column.name}` : 'Add column'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Moving a card into a column that sets a status changes the work item's status.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-name`} className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id={`${idPrefix}-name`}\n className=\"mt-1\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Sets status\n </label>\n <select\n id={`${idPrefix}-status`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={mappedStatus}\n onChange={(e) => setMappedStatus(e.target.value)}\n >\n <option value=\"\">No status (cards keep theirs)</option>\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {statusLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-wip`} className=\"text-sm font-medium\">\n WIP limit (blank for none)\n </label>\n <Input\n id={`${idPrefix}-wip`}\n type=\"number\"\n min={1}\n className=\"mt-1\"\n value={wip}\n onChange={(e) => setWip(e.target.value)}\n />\n {invalidWip && <ErrorLine message=\"The WIP limit must be a whole number, 1 or more.\" />}\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter className=\"gap-2\">\n {editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\" disabled={busy}>\n Delete column\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete {column.name}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its cards are not deleted: each goes back to the column for its status, or off\n the board if no column sets that status.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n try {\n await throwOnError(remove({ variables: { id: column.id } }));\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be deleted.'));\n }\n }}\n >\n Delete column\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n <Button onClick={save} disabled={!canSave} data-testid={`${idPrefix}-save`}>\n {busy ? 'Saving…' : editing ? 'Save' : 'Add column'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|