@burdenoff/microfe-planmagnet 2026.927.6 → 2026.928.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/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +58 -54
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +313 -306
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +47 -40
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.d.ts +9 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +87 -59
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/hooks/useProjectWorkflow.d.ts +32 -0
- package/dist/planmagnet/hooks/useProjectWorkflow.d.ts.map +1 -0
- package/dist/planmagnet/hooks/useProjectWorkflow.js +44 -0
- package/dist/planmagnet/hooks/useProjectWorkflow.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsWorkflowPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsWorkflowPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsWorkflowPagePage.js +261 -0
- package/dist/planmagnet/pages/ProjectsWorkflowPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +8 -9
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { ErrorLine as n } from "../controls/ConfirmButton.js";
|
|
4
|
+
import { PROJECT_WORKFLOW as r, canonicalLabel as i } from "../hooks/useProjectWorkflow.js";
|
|
5
|
+
import { useState as a } from "react";
|
|
6
|
+
import { ArrowLeft as o, Loader2 as s } from "lucide-react";
|
|
7
|
+
import { useParams as c } from "react-router";
|
|
8
|
+
import { gql as l } from "@apollo/client";
|
|
9
|
+
import { useMutation as u, useQuery as d } from "@apollo/client/react";
|
|
10
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
11
|
+
import { Button as m, Card as h, CardContent as g, CardHeader as _, CardTitle as v, Input as y } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/pages/ProjectsWorkflowPagePage.tsx
|
|
13
|
+
var b = l`
|
|
14
|
+
mutation ProjectWorkflowUpdate($projectId: ID!, $input: PlanmagnetUpdateProjectWorkflowInput!) {
|
|
15
|
+
planmagnetUpdateProjectWorkflow(projectId: $projectId, input: $input) {
|
|
16
|
+
projectId
|
|
17
|
+
enforceTransitions
|
|
18
|
+
statuses {
|
|
19
|
+
status
|
|
20
|
+
label
|
|
21
|
+
hidden
|
|
22
|
+
category
|
|
23
|
+
}
|
|
24
|
+
transitions {
|
|
25
|
+
from
|
|
26
|
+
to
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`, x = l`
|
|
31
|
+
query ProjectWorkflowProjectName($id: ID!) {
|
|
32
|
+
planmagnetProject(id: $id) {
|
|
33
|
+
id
|
|
34
|
+
name
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`, S = [
|
|
38
|
+
"BACKLOG",
|
|
39
|
+
"UNSTARTED",
|
|
40
|
+
"STARTED",
|
|
41
|
+
"DONE",
|
|
42
|
+
"CANCELLED"
|
|
43
|
+
], C = new Set(["DONE", "CANCELLED"]);
|
|
44
|
+
function w() {
|
|
45
|
+
let t = e(), i = c().id ?? "", { data: a, loading: l, error: u } = d(r, {
|
|
46
|
+
variables: { projectId: i },
|
|
47
|
+
skip: !i
|
|
48
|
+
}), { data: h } = d(x, {
|
|
49
|
+
variables: { id: i },
|
|
50
|
+
skip: !i
|
|
51
|
+
});
|
|
52
|
+
return l && !a ? /* @__PURE__ */ f("div", {
|
|
53
|
+
className: "flex items-center justify-center p-12",
|
|
54
|
+
children: /* @__PURE__ */ f(s, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
55
|
+
}) : u || !a ? /* @__PURE__ */ f(n, { message: u?.message ?? "This project was not found." }) : /* @__PURE__ */ p("div", {
|
|
56
|
+
className: "space-y-6 p-6",
|
|
57
|
+
children: [/* @__PURE__ */ p("div", {
|
|
58
|
+
className: "flex items-start gap-3",
|
|
59
|
+
children: [/* @__PURE__ */ f(m, {
|
|
60
|
+
variant: "ghost",
|
|
61
|
+
size: "icon",
|
|
62
|
+
"aria-label": "Back to the project",
|
|
63
|
+
onClick: () => t(`/projects/project-detail-page/${i}`),
|
|
64
|
+
children: /* @__PURE__ */ f(o, { className: "h-4 w-4" })
|
|
65
|
+
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ p("h1", {
|
|
66
|
+
className: "text-2xl font-semibold",
|
|
67
|
+
children: ["Workflow", h?.planmagnetProject ? ` — ${h.planmagnetProject.name}` : ""]
|
|
68
|
+
}), /* @__PURE__ */ f("p", {
|
|
69
|
+
className: "text-sm text-muted-foreground",
|
|
70
|
+
children: "Name the statuses, hide the ones this project does not use, and choose which moves are allowed. Reports always count by the standard status, so renaming never breaks them."
|
|
71
|
+
})] })]
|
|
72
|
+
}), /* @__PURE__ */ f(T, { workflow: a.planmagnetProjectWorkflow }, JSON.stringify(a.planmagnetProjectWorkflow))]
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function T({ workflow: e }) {
|
|
76
|
+
let [r, o] = a(e.statuses), [s, c] = a(e.enforceTransitions), [l, d] = a(new Set(e.transitions.map((e) => `${e.from}>${e.to}`))), [x, w] = a(null), [T, E] = a(!1), [D, O] = u(b, {
|
|
77
|
+
refetchQueries: [
|
|
78
|
+
"ProjectWorkflow",
|
|
79
|
+
"ProjectBoard",
|
|
80
|
+
"GetWorkItemDetail"
|
|
81
|
+
],
|
|
82
|
+
awaitRefetchQueries: !0
|
|
83
|
+
}), k = r.filter((e) => !e.hidden), A = (e, t) => {
|
|
84
|
+
E(!1), o(r.map((n) => n.status === e ? {
|
|
85
|
+
...n,
|
|
86
|
+
...t
|
|
87
|
+
} : n));
|
|
88
|
+
}, j = (e, t) => {
|
|
89
|
+
E(!1);
|
|
90
|
+
let n = `${e}>${t}`, r = new Set(l);
|
|
91
|
+
r.has(n) ? r.delete(n) : r.add(n), d(r);
|
|
92
|
+
}, M = r.map((e) => e.label.trim().toLowerCase()), N = M.some((e, t) => e !== "" && M.indexOf(e) !== t), P = r.some((e) => e.label.trim() === ""), F = new Set(r.filter((e) => e.hidden).map((e) => e.status)), I = [...l].map((e) => {
|
|
93
|
+
let [t, n] = e.split(">");
|
|
94
|
+
return {
|
|
95
|
+
from: t,
|
|
96
|
+
to: n
|
|
97
|
+
};
|
|
98
|
+
}).filter((e) => !F.has(e.to)), L = N || P || s && I.length === 0, R = async () => {
|
|
99
|
+
w(null), E(!1);
|
|
100
|
+
try {
|
|
101
|
+
await t(D({ variables: {
|
|
102
|
+
projectId: e.projectId,
|
|
103
|
+
input: {
|
|
104
|
+
enforceTransitions: s,
|
|
105
|
+
statuses: r.map((e) => ({
|
|
106
|
+
status: e.status,
|
|
107
|
+
label: e.label.trim(),
|
|
108
|
+
hidden: e.hidden,
|
|
109
|
+
category: e.category
|
|
110
|
+
})),
|
|
111
|
+
transitions: I
|
|
112
|
+
}
|
|
113
|
+
} })), E(!0);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
w(e instanceof Error ? e.message : "The workflow could not be saved.");
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
return /* @__PURE__ */ p("div", {
|
|
119
|
+
className: "space-y-6",
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ p(h, { children: [/* @__PURE__ */ f(_, { children: /* @__PURE__ */ f(v, { children: "Statuses" }) }), /* @__PURE__ */ p(g, { children: [
|
|
122
|
+
/* @__PURE__ */ p("table", {
|
|
123
|
+
className: "w-full text-sm",
|
|
124
|
+
"data-testid": "workflow-statuses",
|
|
125
|
+
children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", {
|
|
126
|
+
className: "text-left text-muted-foreground",
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ f("th", {
|
|
129
|
+
className: "py-1 pr-3 font-medium",
|
|
130
|
+
children: "Standard status"
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ f("th", {
|
|
133
|
+
className: "py-1 pr-3 font-medium",
|
|
134
|
+
children: "Name in this project"
|
|
135
|
+
}),
|
|
136
|
+
/* @__PURE__ */ f("th", {
|
|
137
|
+
className: "py-1 pr-3 font-medium",
|
|
138
|
+
children: "Reports as"
|
|
139
|
+
}),
|
|
140
|
+
/* @__PURE__ */ f("th", {
|
|
141
|
+
className: "py-1 font-medium",
|
|
142
|
+
children: "Hidden"
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
}) }), /* @__PURE__ */ f("tbody", { children: r.map((e) => /* @__PURE__ */ p("tr", {
|
|
146
|
+
className: "border-t",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ f("td", {
|
|
149
|
+
className: "py-2 pr-3 text-muted-foreground",
|
|
150
|
+
children: i(e.status)
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ f("td", {
|
|
153
|
+
className: "py-2 pr-3",
|
|
154
|
+
children: /* @__PURE__ */ f(y, {
|
|
155
|
+
"aria-label": `Name for ${i(e.status)}`,
|
|
156
|
+
maxLength: 40,
|
|
157
|
+
value: e.label,
|
|
158
|
+
onChange: (t) => A(e.status, { label: t.target.value })
|
|
159
|
+
})
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ f("td", {
|
|
162
|
+
className: "py-2 pr-3",
|
|
163
|
+
children: /* @__PURE__ */ f("select", {
|
|
164
|
+
"aria-label": `Category for ${i(e.status)}`,
|
|
165
|
+
className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
|
|
166
|
+
value: e.category,
|
|
167
|
+
disabled: C.has(e.status),
|
|
168
|
+
title: C.has(e.status) ? "Velocity and progress read Done and Cancelled" : void 0,
|
|
169
|
+
onChange: (t) => A(e.status, { category: t.target.value }),
|
|
170
|
+
children: S.filter((t) => C.has(e.status) || !C.has(t)).map((e) => /* @__PURE__ */ f("option", {
|
|
171
|
+
value: e,
|
|
172
|
+
children: i(e)
|
|
173
|
+
}, e))
|
|
174
|
+
})
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ f("td", {
|
|
177
|
+
className: "py-2",
|
|
178
|
+
children: /* @__PURE__ */ f("input", {
|
|
179
|
+
type: "checkbox",
|
|
180
|
+
"aria-label": `Hide ${i(e.status)}`,
|
|
181
|
+
checked: e.hidden,
|
|
182
|
+
disabled: e.status === "DONE",
|
|
183
|
+
title: e.status === "DONE" ? "Done cannot be hidden" : void 0,
|
|
184
|
+
onChange: (t) => A(e.status, { hidden: t.target.checked })
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
]
|
|
188
|
+
}, e.status)) })]
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ f("p", {
|
|
191
|
+
className: "mt-2 text-xs text-muted-foreground",
|
|
192
|
+
children: "A hidden status cannot be chosen for new work; items already in it stay visible until they move."
|
|
193
|
+
}),
|
|
194
|
+
P && /* @__PURE__ */ f(n, { message: "Every status needs a name." }),
|
|
195
|
+
N && /* @__PURE__ */ f(n, { message: "Two statuses cannot share a name." })
|
|
196
|
+
] })] }),
|
|
197
|
+
/* @__PURE__ */ p(h, { children: [/* @__PURE__ */ f(_, { children: /* @__PURE__ */ f(v, { children: "Allowed moves" }) }), /* @__PURE__ */ p(g, {
|
|
198
|
+
className: "space-y-3",
|
|
199
|
+
children: [/* @__PURE__ */ p("label", {
|
|
200
|
+
className: "flex items-center gap-2 text-sm",
|
|
201
|
+
children: [/* @__PURE__ */ f("input", {
|
|
202
|
+
type: "checkbox",
|
|
203
|
+
"data-testid": "workflow-enforce",
|
|
204
|
+
checked: s,
|
|
205
|
+
onChange: (e) => {
|
|
206
|
+
E(!1), c(e.target.checked);
|
|
207
|
+
}
|
|
208
|
+
}), "Only allow the moves ticked below (on the task page, the board, bulk changes, the API and automation)"]
|
|
209
|
+
}), s && /* @__PURE__ */ p("div", {
|
|
210
|
+
className: "overflow-x-auto",
|
|
211
|
+
children: [/* @__PURE__ */ p("table", {
|
|
212
|
+
className: "text-sm",
|
|
213
|
+
"data-testid": "workflow-transitions",
|
|
214
|
+
children: [/* @__PURE__ */ f("thead", { children: /* @__PURE__ */ p("tr", { children: [/* @__PURE__ */ f("th", {
|
|
215
|
+
className: "py-1 pr-3 text-left font-medium text-muted-foreground",
|
|
216
|
+
children: "From ↓ / To →"
|
|
217
|
+
}), k.map((e) => /* @__PURE__ */ f("th", {
|
|
218
|
+
className: "px-2 py-1 font-medium",
|
|
219
|
+
children: e.label
|
|
220
|
+
}, e.status))] }) }), /* @__PURE__ */ f("tbody", { children: r.map((e) => /* @__PURE__ */ p("tr", {
|
|
221
|
+
className: "border-t",
|
|
222
|
+
children: [/* @__PURE__ */ f("td", {
|
|
223
|
+
className: "py-1 pr-3",
|
|
224
|
+
children: e.label
|
|
225
|
+
}), k.map((t) => /* @__PURE__ */ f("td", {
|
|
226
|
+
className: "px-2 py-1 text-center",
|
|
227
|
+
children: e.status === t.status ? /* @__PURE__ */ f("span", {
|
|
228
|
+
className: "text-muted-foreground",
|
|
229
|
+
children: "—"
|
|
230
|
+
}) : /* @__PURE__ */ f("input", {
|
|
231
|
+
type: "checkbox",
|
|
232
|
+
"aria-label": `Allow ${e.label} to ${t.label}`,
|
|
233
|
+
checked: l.has(`${e.status}>${t.status}`),
|
|
234
|
+
onChange: () => j(e.status, t.status)
|
|
235
|
+
})
|
|
236
|
+
}, t.status))]
|
|
237
|
+
}, e.status)) })]
|
|
238
|
+
}), I.length === 0 && /* @__PURE__ */ f(n, { message: "Tick at least one move, or stop enforcing." })]
|
|
239
|
+
})]
|
|
240
|
+
})] }),
|
|
241
|
+
/* @__PURE__ */ p("div", {
|
|
242
|
+
className: "flex items-center gap-3",
|
|
243
|
+
children: [/* @__PURE__ */ f(m, {
|
|
244
|
+
"data-testid": "workflow-save",
|
|
245
|
+
disabled: L || O.loading,
|
|
246
|
+
onClick: () => void R(),
|
|
247
|
+
children: O.loading ? "Saving…" : "Save workflow"
|
|
248
|
+
}), T && /* @__PURE__ */ f("span", {
|
|
249
|
+
role: "status",
|
|
250
|
+
className: "text-sm text-status-success-text",
|
|
251
|
+
children: "Saved."
|
|
252
|
+
})]
|
|
253
|
+
}),
|
|
254
|
+
x && /* @__PURE__ */ f(n, { message: x })
|
|
255
|
+
]
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { w as ProjectsWorkflowPagePage };
|
|
260
|
+
|
|
261
|
+
//# sourceMappingURL=ProjectsWorkflowPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectsWorkflowPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsWorkflowPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useParams } from 'react-router';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Loader2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport {\n PROJECT_WORKFLOW,\n type ProjectWorkflow,\n canonicalLabel,\n} from '../hooks/useProjectWorkflow';\n\n/**\n * A project's workflow (BUILD_PLAN P1.3, BOFF-8043). The seven statuses are fixed; here a project names them, hides\n * the ones it does not use, groups them for reports, and — optionally — allows only the moves it lists. The server\n * enforces the rules on every path (task page, board, bulk, API, MCP, automation, sync) and checks the whole\n * configuration before saving any of it.\n */\n\nconst UPDATE = gql`\n mutation ProjectWorkflowUpdate($projectId: ID!, $input: PlanmagnetUpdateProjectWorkflowInput!) {\n planmagnetUpdateProjectWorkflow(projectId: $projectId, input: $input) {\n projectId\n enforceTransitions\n statuses {\n status\n label\n hidden\n category\n }\n transitions {\n from\n to\n }\n }\n }\n`;\nconst PROJECT_NAME = gql`\n query ProjectWorkflowProjectName($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\nconst CATEGORIES = ['BACKLOG', 'UNSTARTED', 'STARTED', 'DONE', 'CANCELLED'] as const;\nconst CLOSED = new Set(['DONE', 'CANCELLED']);\n\ntype Row = ProjectWorkflow['statuses'][number];\n\nexport function ProjectsWorkflowPagePage() {\n const navigate = usePlanMagnetNavigate();\n const projectId = useParams<{ id: string }>().id ?? '';\n const { data, loading, error } = useQuery<{ planmagnetProjectWorkflow: ProjectWorkflow }>(\n PROJECT_WORKFLOW,\n {\n variables: { projectId },\n skip: !projectId,\n }\n );\n const { data: project } = useQuery<{ planmagnetProject: { id: string; name: string } | null }>(\n PROJECT_NAME,\n {\n variables: { id: projectId },\n skip: !projectId,\n }\n );\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n if (error || !data) {\n return <ErrorLine message={error?.message ?? 'This project was not found.'} />;\n }\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start gap-3\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Back to the project\"\n onClick={() => navigate(`/projects/project-detail-page/${projectId}`)}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n Workflow{project?.planmagnetProject ? ` — ${project.planmagnetProject.name}` : ''}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Name the statuses, hide the ones this project does not use, and choose which moves are\n allowed. Reports always count by the standard status, so renaming never breaks them.\n </p>\n </div>\n </div>\n {/* Keyed on the loaded workflow: after a save the editor starts from what the server stored. */}\n <WorkflowEditor\n key={JSON.stringify(data.planmagnetProjectWorkflow)}\n workflow={data.planmagnetProjectWorkflow}\n />\n </div>\n );\n}\n\nfunction WorkflowEditor({ workflow }: { workflow: ProjectWorkflow }) {\n const [rows, setRows] = useState<Row[]>(workflow.statuses);\n const [enforce, setEnforce] = useState(workflow.enforceTransitions);\n const [allowed, setAllowed] = useState<Set<string>>(\n new Set(workflow.transitions.map((t) => `${t.from}>${t.to}`))\n );\n const [error, setError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n const [save, state] = useMutation(UPDATE, {\n refetchQueries: ['ProjectWorkflow', 'ProjectBoard', 'GetWorkItemDetail'],\n awaitRefetchQueries: true,\n });\n\n const visible = rows.filter((r) => !r.hidden);\n const setRow = (status: string, patch: Partial<Row>) => {\n setSaved(false);\n setRows(rows.map((r) => (r.status === status ? { ...r, ...patch } : r)));\n };\n const toggle = (from: string, to: string) => {\n setSaved(false);\n const key = `${from}>${to}`;\n const next = new Set(allowed);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n setAllowed(next);\n };\n const labels = rows.map((r) => r.label.trim().toLowerCase());\n const duplicate = labels.some((l, i) => l !== '' && labels.indexOf(l) !== i);\n const blank = rows.some((r) => r.label.trim() === '');\n const hiddenTargets = new Set(rows.filter((r) => r.hidden).map((r) => r.status));\n const transitions = [...allowed]\n .map((key) => {\n const [from, to] = key.split('>') as [string, string];\n return { from, to };\n })\n .filter((t) => !hiddenTargets.has(t.to));\n const invalid = duplicate || blank || (enforce && transitions.length === 0);\n\n const submit = async () => {\n setError(null);\n setSaved(false);\n try {\n await throwOnError(\n save({\n variables: {\n projectId: workflow.projectId,\n input: {\n enforceTransitions: enforce,\n statuses: rows.map((r) => ({\n status: r.status,\n label: r.label.trim(),\n hidden: r.hidden,\n category: r.category,\n })),\n transitions,\n },\n },\n })\n );\n setSaved(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The workflow could not be saved.');\n }\n };\n\n return (\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Statuses</CardTitle>\n </CardHeader>\n <CardContent>\n <table className=\"w-full text-sm\" data-testid=\"workflow-statuses\">\n <thead>\n <tr className=\"text-left text-muted-foreground\">\n <th className=\"py-1 pr-3 font-medium\">Standard status</th>\n <th className=\"py-1 pr-3 font-medium\">Name in this project</th>\n <th className=\"py-1 pr-3 font-medium\">Reports as</th>\n <th className=\"py-1 font-medium\">Hidden</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((r) => (\n <tr key={r.status} className=\"border-t\">\n <td className=\"py-2 pr-3 text-muted-foreground\">{canonicalLabel(r.status)}</td>\n <td className=\"py-2 pr-3\">\n <Input\n aria-label={`Name for ${canonicalLabel(r.status)}`}\n maxLength={40}\n value={r.label}\n onChange={(e) => setRow(r.status, { label: e.target.value })}\n />\n </td>\n <td className=\"py-2 pr-3\">\n <select\n aria-label={`Category for ${canonicalLabel(r.status)}`}\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={r.category}\n disabled={CLOSED.has(r.status)}\n title={\n CLOSED.has(r.status)\n ? 'Velocity and progress read Done and Cancelled'\n : undefined\n }\n onChange={(e) => setRow(r.status, { category: e.target.value })}\n >\n {CATEGORIES.filter((c) => CLOSED.has(r.status) || !CLOSED.has(c)).map((c) => (\n <option key={c} value={c}>\n {canonicalLabel(c)}\n </option>\n ))}\n </select>\n </td>\n <td className=\"py-2\">\n <input\n type=\"checkbox\"\n aria-label={`Hide ${canonicalLabel(r.status)}`}\n checked={r.hidden}\n disabled={r.status === 'DONE'}\n title={r.status === 'DONE' ? 'Done cannot be hidden' : undefined}\n onChange={(e) => setRow(r.status, { hidden: e.target.checked })}\n />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n <p className=\"mt-2 text-xs text-muted-foreground\">\n A hidden status cannot be chosen for new work; items already in it stay visible until\n they move.\n </p>\n {blank && <ErrorLine message=\"Every status needs a name.\" />}\n {duplicate && <ErrorLine message=\"Two statuses cannot share a name.\" />}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Allowed moves</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n data-testid=\"workflow-enforce\"\n checked={enforce}\n onChange={(e) => {\n setSaved(false);\n setEnforce(e.target.checked);\n }}\n />\n Only allow the moves ticked below (on the task page, the board, bulk changes, the API\n and automation)\n </label>\n {enforce && (\n <div className=\"overflow-x-auto\">\n <table className=\"text-sm\" data-testid=\"workflow-transitions\">\n <thead>\n <tr>\n <th className=\"py-1 pr-3 text-left font-medium text-muted-foreground\">\n From ↓ / To →\n </th>\n {visible.map((to) => (\n <th key={to.status} className=\"px-2 py-1 font-medium\">\n {to.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((from) => (\n <tr key={from.status} className=\"border-t\">\n <td className=\"py-1 pr-3\">{from.label}</td>\n {visible.map((to) => (\n <td key={to.status} className=\"px-2 py-1 text-center\">\n {from.status === to.status ? (\n <span className=\"text-muted-foreground\">—</span>\n ) : (\n <input\n type=\"checkbox\"\n aria-label={`Allow ${from.label} to ${to.label}`}\n checked={allowed.has(`${from.status}>${to.status}`)}\n onChange={() => toggle(from.status, to.status)}\n />\n )}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {transitions.length === 0 && (\n <ErrorLine message=\"Tick at least one move, or stop enforcing.\" />\n )}\n </div>\n )}\n </CardContent>\n </Card>\n\n <div className=\"flex items-center gap-3\">\n <Button\n data-testid=\"workflow-save\"\n disabled={invalid || state.loading}\n onClick={() => void submit()}\n >\n {state.loading ? 'Saving…' : 'Save workflow'}\n </Button>\n {saved && (\n <span role=\"status\" className=\"text-sm text-status-success-text\">\n Saved.\n </span>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TasksTaskDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TasksTaskDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"names":[],"mappings":"AAgVA,wBAAgB,uBAAuB,4CAqItC"}
|
|
@@ -3,7 +3,7 @@ import { PersonName as n } from "../people/People.js";
|
|
|
3
3
|
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
4
4
|
import { DocSearchSelect as i } from "../controls/Pickers.js";
|
|
5
5
|
import { MarkdownEditor as a } from "../../components/MarkdownEditor.js";
|
|
6
|
-
import { WORK_ITEM_PRIORITIES as o,
|
|
6
|
+
import { WORK_ITEM_PRIORITIES as o, WorkItemAssigneeSelect as s, WorkItemEnumSelect as c, WorkItemHeaderActions as l, WorkItemStatusSelect as u } from "../controls/WorkItemControls.js";
|
|
7
7
|
import { WorkItemComments as d } from "../comments/WorkItemComments.js";
|
|
8
8
|
import { WorkItemActivity as f } from "../comments/WorkItemActivity.js";
|
|
9
9
|
import { useState as p } from "react";
|
|
@@ -17,6 +17,7 @@ var A = v`
|
|
|
17
17
|
query GetWorkItemDetail($workItemId: ID!) {
|
|
18
18
|
planmagnetWorkItem(id: $workItemId) {
|
|
19
19
|
id
|
|
20
|
+
projectId
|
|
20
21
|
title
|
|
21
22
|
description
|
|
22
23
|
type
|
|
@@ -270,7 +271,7 @@ function L() {
|
|
|
270
271
|
" · ",
|
|
271
272
|
m.type.toLowerCase()
|
|
272
273
|
]
|
|
273
|
-
})] }), /* @__PURE__ */ x(
|
|
274
|
+
})] }), /* @__PURE__ */ x(l, {
|
|
274
275
|
workItemId: m.id,
|
|
275
276
|
title: m.title
|
|
276
277
|
})]
|
|
@@ -297,12 +298,10 @@ function L() {
|
|
|
297
298
|
children: [/* @__PURE__ */ x("span", {
|
|
298
299
|
className: "text-muted-foreground",
|
|
299
300
|
children: "Status"
|
|
300
|
-
}), /* @__PURE__ */ x(
|
|
301
|
+
}), /* @__PURE__ */ x(u, {
|
|
301
302
|
workItemId: m.id,
|
|
302
|
-
|
|
303
|
-
value: m.status
|
|
304
|
-
options: s,
|
|
305
|
-
label: "Status"
|
|
303
|
+
projectId: m.projectId,
|
|
304
|
+
value: m.status
|
|
306
305
|
})]
|
|
307
306
|
}),
|
|
308
307
|
/* @__PURE__ */ S("div", {
|
|
@@ -310,7 +309,7 @@ function L() {
|
|
|
310
309
|
children: [/* @__PURE__ */ x("span", {
|
|
311
310
|
className: "text-muted-foreground",
|
|
312
311
|
children: "Priority"
|
|
313
|
-
}), /* @__PURE__ */ x(
|
|
312
|
+
}), /* @__PURE__ */ x(c, {
|
|
314
313
|
workItemId: m.id,
|
|
315
314
|
field: "priority",
|
|
316
315
|
value: m.priority,
|
|
@@ -344,7 +343,7 @@ function L() {
|
|
|
344
343
|
children: [/* @__PURE__ */ x("span", {
|
|
345
344
|
className: "text-muted-foreground",
|
|
346
345
|
children: "Assignee"
|
|
347
|
-
}), /* @__PURE__ */ x(
|
|
346
|
+
}), /* @__PURE__ */ x(s, {
|
|
348
347
|
workItemId: m.id,
|
|
349
348
|
assigneeId: m.assigneeId
|
|
350
349
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport {\n WORK_ITEM_PRIORITIES,\n WORK_ITEM_STATUSES,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} · {task.type.toLowerCase()}\n </p>\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <LinkedDocsPanel workItemId={task.id} />\n <WorkItemComments workItemId={task.id} />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"status\"\n value={task.status}\n options={WORK_ITEM_STATUSES}\n label=\"Status\"\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport {\n WORK_ITEM_PRIORITIES,\n WorkItemStatusSelect,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n projectId\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} · {task.type.toLowerCase()}\n </p>\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <LinkedDocsPanel workItemId={task.id} />\n <WorkItemComments workItemId={task.id} />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemStatusSelect\n workItemId={task.id}\n projectId={task.projectId}\n value={task.status}\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|