@burdenoff/microfe-planmagnet 2026.927.2 → 2026.927.4
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/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +128 -124
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
- package/dist/planmagnet/controls/ConfirmButton.d.ts +25 -0
- package/dist/planmagnet/controls/ConfirmButton.d.ts.map +1 -0
- package/dist/planmagnet/controls/ConfirmButton.js +30 -0
- package/dist/planmagnet/controls/ConfirmButton.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.d.ts +26 -0
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +318 -0
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts +17 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/MilestoneControls.js +200 -0
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -0
- package/dist/planmagnet/controls/ObjectiveControls.d.ts +31 -0
- package/dist/planmagnet/controls/ObjectiveControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ObjectiveControls.js +296 -0
- package/dist/planmagnet/controls/ObjectiveControls.js.map +1 -0
- package/dist/planmagnet/controls/Pickers.d.ts +26 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -0
- package/dist/planmagnet/controls/Pickers.js +171 -0
- package/dist/planmagnet/controls/Pickers.js.map +1 -0
- package/dist/planmagnet/controls/ProjectControls.d.ts +13 -0
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProjectControls.js +104 -0
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -0
- package/dist/planmagnet/controls/ReleaseControls.d.ts +14 -0
- package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ReleaseControls.js +108 -0
- package/dist/planmagnet/controls/ReleaseControls.js.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts +52 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.js +198 -0
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -0
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +188 -154
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocsPagePage.js +4 -4
- package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.js +63 -56
- package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +229 -214
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +142 -114
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditReleasePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditReleasePagePage.js +134 -105
- package/dist/planmagnet/pages/ProductsEditReleasePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +102 -108
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +119 -94
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +97 -90
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +107 -106
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -0
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +150 -111
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +116 -93
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +55 -63
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +134 -132
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +6 -5
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +167 -116
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +139 -107
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +205 -158
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +1 -0
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/people/People.js +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts +34 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js +301 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -0
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +194 -215
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { useDeferredValue as t, useState as n } from "react";
|
|
3
|
+
import { gql as r } from "@apollo/client";
|
|
4
|
+
import { useQuery as i } from "@apollo/client/react";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { Input as s } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/planmagnet/controls/Pickers.tsx
|
|
8
|
+
var c = "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60", l = r`
|
|
9
|
+
query PickerProjects($workspaceId: ID!) {
|
|
10
|
+
planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {
|
|
11
|
+
items {
|
|
12
|
+
id
|
|
13
|
+
name
|
|
14
|
+
}
|
|
15
|
+
totalCount
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
function u({ id: t, value: n, onChange: r, noneLabel: s }) {
|
|
20
|
+
let { workspaceId: u } = e(), { data: d, loading: f, error: p } = i(l, {
|
|
21
|
+
variables: { workspaceId: u ?? "" },
|
|
22
|
+
skip: !u
|
|
23
|
+
}), m = d?.planmagnetProjects.items ?? [];
|
|
24
|
+
return /* @__PURE__ */ o("select", {
|
|
25
|
+
id: t,
|
|
26
|
+
className: c,
|
|
27
|
+
value: n,
|
|
28
|
+
onChange: (e) => r(e.target.value),
|
|
29
|
+
children: [
|
|
30
|
+
s !== void 0 && /* @__PURE__ */ a("option", {
|
|
31
|
+
value: "",
|
|
32
|
+
children: s
|
|
33
|
+
}),
|
|
34
|
+
m.length === 0 && s === void 0 && /* @__PURE__ */ a("option", {
|
|
35
|
+
value: "",
|
|
36
|
+
children: f && !d ? "Loading projects…" : p ? "Projects could not be loaded" : "No projects yet"
|
|
37
|
+
}),
|
|
38
|
+
m.map((e) => /* @__PURE__ */ a("option", {
|
|
39
|
+
value: e.id,
|
|
40
|
+
children: e.name
|
|
41
|
+
}, e.id))
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
var d = r`
|
|
46
|
+
query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {
|
|
47
|
+
planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {
|
|
48
|
+
items {
|
|
49
|
+
id
|
|
50
|
+
key
|
|
51
|
+
title
|
|
52
|
+
}
|
|
53
|
+
totalCount
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
function f({ id: r, projectId: l, value: u, onChange: f, exclude: p = [] }) {
|
|
58
|
+
let { workspaceId: m } = e(), [h, g] = n(""), _ = t(h.trim()), { data: v, loading: y } = i(d, {
|
|
59
|
+
variables: {
|
|
60
|
+
workspaceId: m ?? "",
|
|
61
|
+
filter: {
|
|
62
|
+
..._ && { search: _ },
|
|
63
|
+
...l && { projectId: l }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
skip: !m
|
|
67
|
+
}), b = v?.planmagnetWorkItems.items ?? [], x = b.filter((e) => !p.includes(e.id)), S = v?.planmagnetWorkItems.totalCount ?? 0;
|
|
68
|
+
return /* @__PURE__ */ o("div", {
|
|
69
|
+
className: "space-y-1",
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ a(s, {
|
|
72
|
+
"aria-label": "Search work items",
|
|
73
|
+
placeholder: "Search by key or title",
|
|
74
|
+
value: h,
|
|
75
|
+
onChange: (e) => g(e.target.value)
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ o("select", {
|
|
78
|
+
id: r,
|
|
79
|
+
className: c,
|
|
80
|
+
value: u,
|
|
81
|
+
onChange: (e) => f(e.target.value),
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ a("option", {
|
|
84
|
+
value: "",
|
|
85
|
+
children: y && !v ? "Searching…" : x.length ? "Choose a work item" : "No match"
|
|
86
|
+
}),
|
|
87
|
+
u && !x.some((e) => e.id === u) && /* @__PURE__ */ a("option", {
|
|
88
|
+
value: u,
|
|
89
|
+
children: "Currently linked work item"
|
|
90
|
+
}),
|
|
91
|
+
x.map((e) => /* @__PURE__ */ o("option", {
|
|
92
|
+
value: e.id,
|
|
93
|
+
children: [
|
|
94
|
+
e.key,
|
|
95
|
+
" — ",
|
|
96
|
+
e.title
|
|
97
|
+
]
|
|
98
|
+
}, e.id))
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
101
|
+
S > b.length && /* @__PURE__ */ o("p", {
|
|
102
|
+
className: "text-xs text-muted-foreground",
|
|
103
|
+
children: [
|
|
104
|
+
"Showing ",
|
|
105
|
+
b.length,
|
|
106
|
+
" of ",
|
|
107
|
+
S,
|
|
108
|
+
" — type to narrow the list."
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
var p = r`
|
|
115
|
+
query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {
|
|
116
|
+
planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {
|
|
117
|
+
items {
|
|
118
|
+
id
|
|
119
|
+
title
|
|
120
|
+
}
|
|
121
|
+
totalCount
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
function m({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
126
|
+
let { workspaceId: f } = e(), [m, h] = n(""), g = t(m.trim()), { data: _, loading: v } = i(p, {
|
|
127
|
+
variables: {
|
|
128
|
+
workspaceId: f ?? "",
|
|
129
|
+
filter: g ? { search: g } : void 0
|
|
130
|
+
},
|
|
131
|
+
skip: !f
|
|
132
|
+
}), y = _?.planmagnetDocs.items ?? [], b = y.filter((e) => !d.includes(e.id)), x = _?.planmagnetDocs.totalCount ?? 0;
|
|
133
|
+
return /* @__PURE__ */ o("div", {
|
|
134
|
+
className: "space-y-1",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ a(s, {
|
|
137
|
+
"aria-label": "Search docs",
|
|
138
|
+
placeholder: "Search docs by title",
|
|
139
|
+
value: m,
|
|
140
|
+
onChange: (e) => h(e.target.value)
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ o("select", {
|
|
143
|
+
id: r,
|
|
144
|
+
className: c,
|
|
145
|
+
value: l,
|
|
146
|
+
onChange: (e) => u(e.target.value),
|
|
147
|
+
children: [/* @__PURE__ */ a("option", {
|
|
148
|
+
value: "",
|
|
149
|
+
children: v && !_ ? "Searching…" : b.length ? "Choose a doc" : "No match"
|
|
150
|
+
}), b.map((e) => /* @__PURE__ */ a("option", {
|
|
151
|
+
value: e.id,
|
|
152
|
+
children: e.title
|
|
153
|
+
}, e.id))]
|
|
154
|
+
}),
|
|
155
|
+
x > y.length && /* @__PURE__ */ o("p", {
|
|
156
|
+
className: "text-xs text-muted-foreground",
|
|
157
|
+
children: [
|
|
158
|
+
"Showing ",
|
|
159
|
+
y.length,
|
|
160
|
+
" of ",
|
|
161
|
+
x,
|
|
162
|
+
" — type to narrow the list."
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { m as DocSearchSelect, u as ProjectSelect, f as WorkItemSearchSelect };
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=Pickers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProjects: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROJECTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const projects = data?.planmagnetProjects.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project controls (BOFF-7940). A new project starts in PLANNING and, until 2026-09-27, nothing in the UI could
|
|
3
|
+
* move it: no status control, and the Edit Project page (which had no Status field) was linked from nowhere. So
|
|
4
|
+
* Projects > Active, the Dashboard's project status card and the admin Bulk Archive never showed a project created
|
|
5
|
+
* in the UI.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PROJECT_STATUSES: readonly ["PLANNING", "ACTIVE", "ON_HOLD", "COMPLETED", "CANCELLED", "ARCHIVED"];
|
|
8
|
+
export declare function ProjectHeaderActions({ projectId, name, status, }: {
|
|
9
|
+
projectId: string;
|
|
10
|
+
name: string;
|
|
11
|
+
status: string;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=ProjectControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/ProjectControls.tsx"],"names":[],"mappings":"AAWA;;;;;GAKG;AAEH,eAAO,MAAM,gBAAgB,kFAOnB,CAAC;AAyBX,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,IAAI,EACJ,MAAM,GACP,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,2CA4EA"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { ConfirmButton as n, ErrorLine as r } from "./ConfirmButton.js";
|
|
4
|
+
import { enumLabel as i } from "./WorkItemControls.js";
|
|
5
|
+
import { detailPath as a } from "../utils/rowNavigation.js";
|
|
6
|
+
import { useState as o } from "react";
|
|
7
|
+
import { Pencil as s, Trash2 as c } from "lucide-react";
|
|
8
|
+
import { gql as l } from "@apollo/client";
|
|
9
|
+
import { useMutation as u } from "@apollo/client/react";
|
|
10
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
11
|
+
import { Button as p } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/controls/ProjectControls.tsx
|
|
13
|
+
var m = [
|
|
14
|
+
"PLANNING",
|
|
15
|
+
"ACTIVE",
|
|
16
|
+
"ON_HOLD",
|
|
17
|
+
"COMPLETED",
|
|
18
|
+
"CANCELLED",
|
|
19
|
+
"ARCHIVED"
|
|
20
|
+
], h = l`
|
|
21
|
+
mutation ProjectControlsStatus($id: ID!, $input: PlanmagnetUpdateProjectInput!) {
|
|
22
|
+
planmagnetUpdateProject(id: $id, input: $input) {
|
|
23
|
+
id
|
|
24
|
+
status
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`, g = l`
|
|
28
|
+
mutation ProjectControlsDelete($id: ID!) {
|
|
29
|
+
planmagnetDeleteProject(id: $id)
|
|
30
|
+
}
|
|
31
|
+
`, _ = [
|
|
32
|
+
"GetProjects",
|
|
33
|
+
"GetActiveProjects",
|
|
34
|
+
"GetBoardProjects",
|
|
35
|
+
"GetDashboardData",
|
|
36
|
+
"AdminPlanmagnetProjects"
|
|
37
|
+
];
|
|
38
|
+
function v({ projectId: l, name: v, status: y }) {
|
|
39
|
+
let b = e(), [x, S] = u(h, {
|
|
40
|
+
refetchQueries: ["GetProjectDetail", ..._],
|
|
41
|
+
awaitRefetchQueries: !0
|
|
42
|
+
}), [C, w] = u(g, { refetchQueries: _ }), [T, E] = o(null), D = S.loading || w.loading, O = m.includes(y) ? m : [y, ...m], k = async (e) => {
|
|
43
|
+
E(null);
|
|
44
|
+
try {
|
|
45
|
+
await t(x({ variables: {
|
|
46
|
+
id: l,
|
|
47
|
+
input: { status: e }
|
|
48
|
+
} }));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
E(e instanceof Error ? e.message : "The status could not be changed.");
|
|
51
|
+
}
|
|
52
|
+
}, A = async () => {
|
|
53
|
+
E(null);
|
|
54
|
+
try {
|
|
55
|
+
await t(C({ variables: { id: l } })), b("/projects/projects-page");
|
|
56
|
+
} catch (e) {
|
|
57
|
+
E(e instanceof Error ? e.message : "The project could not be deleted.");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return /* @__PURE__ */ f("div", {
|
|
61
|
+
className: "flex flex-col items-end gap-2",
|
|
62
|
+
children: [/* @__PURE__ */ f("div", {
|
|
63
|
+
className: "flex flex-wrap items-center justify-end gap-2",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ d("select", {
|
|
66
|
+
"aria-label": "Project status",
|
|
67
|
+
"data-testid": "project-status",
|
|
68
|
+
className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
|
|
69
|
+
value: y,
|
|
70
|
+
disabled: D,
|
|
71
|
+
onChange: (e) => {
|
|
72
|
+
e.target.value !== y && k(e.target.value);
|
|
73
|
+
},
|
|
74
|
+
children: O.map((e) => /* @__PURE__ */ d("option", {
|
|
75
|
+
value: e,
|
|
76
|
+
children: i(e)
|
|
77
|
+
}, e))
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ f(p, {
|
|
80
|
+
size: "sm",
|
|
81
|
+
variant: "outline",
|
|
82
|
+
"data-testid": "project-edit",
|
|
83
|
+
onClick: () => b(a("/projects/edit-project-page", l)),
|
|
84
|
+
children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ d(n, {
|
|
87
|
+
label: "Delete",
|
|
88
|
+
variant: "outline",
|
|
89
|
+
icon: /* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }),
|
|
90
|
+
title: `Delete ${v}?`,
|
|
91
|
+
description: "The project is deleted. Its tasks, sprints, boards, milestones and budgets disappear from every list, and so do releases that belong to no other project. This cannot be undone here.",
|
|
92
|
+
confirmLabel: "Delete project",
|
|
93
|
+
destructive: !0,
|
|
94
|
+
disabled: D,
|
|
95
|
+
onConfirm: () => void A()
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
}), T && /* @__PURE__ */ d(r, { message: T })]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { m as PROJECT_STATUSES, v as ProjectHeaderActions };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=ProjectControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectControls.js","names":[],"sources":["../../../src/planmagnet/controls/ProjectControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Project controls (BOFF-7940). A new project starts in PLANNING and, until 2026-09-27, nothing in the UI could\n * move it: no status control, and the Edit Project page (which had no Status field) was linked from nowhere. So\n * Projects > Active, the Dashboard's project status card and the admin Bulk Archive never showed a project created\n * in the UI.\n */\n\nexport const PROJECT_STATUSES = [\n 'PLANNING',\n 'ACTIVE',\n 'ON_HOLD',\n 'COMPLETED',\n 'CANCELLED',\n 'ARCHIVED',\n] as const;\n\nconst UPDATE_PROJECT_STATUS = gql`\n mutation ProjectControlsStatus($id: ID!, $input: PlanmagnetUpdateProjectInput!) {\n planmagnetUpdateProject(id: $id, input: $input) {\n id\n status\n }\n }\n`;\nconst DELETE_PROJECT = gql`\n mutation ProjectControlsDelete($id: ID!) {\n planmagnetDeleteProject(id: $id)\n }\n`;\n\n/** Every query that lists projects or shows a project's status. */\nconst PROJECT_LIST_QUERIES = [\n 'GetProjects',\n 'GetActiveProjects',\n 'GetBoardProjects',\n 'GetDashboardData',\n 'AdminPlanmagnetProjects',\n];\n\nexport function ProjectHeaderActions({\n projectId,\n name,\n status,\n}: {\n projectId: string;\n name: string;\n status: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [updateStatus, statusState] = useMutation(UPDATE_PROJECT_STATUS, {\n refetchQueries: ['GetProjectDetail', ...PROJECT_LIST_QUERIES],\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE_PROJECT, {\n refetchQueries: PROJECT_LIST_QUERIES,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = statusState.loading || removeState.loading;\n const statuses = (PROJECT_STATUSES as readonly string[]).includes(status)\n ? PROJECT_STATUSES\n : [status, ...PROJECT_STATUSES];\n\n const onStatus = async (next: string) => {\n setError(null);\n try {\n await throwOnError(updateStatus({ variables: { id: projectId, input: { status: next } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: projectId } }));\n navigate('/projects/projects-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The project could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Project status\"\n data-testid=\"project-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"project-edit\"\n onClick={() => navigate(detailPath('/projects/edit-project-page', projectId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${name}?`}\n description=\"The project is deleted. Its tasks, sprints, boards, milestones and budgets disappear from every list, and so do releases that belong to no other project. This cannot be undone here.\"\n confirmLabel=\"Delete project\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAa,IAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAwB,CAAG;;;;;;;GAQ3B,IAAiB,CAAG;;;;GAOpB,IAAuB;CAC3B;CACA;CACA;CACA;CACA;CACD;AAED,SAAgB,EAAqB,EACnC,cACA,SACA,aAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAc,KAAe,EAAY,GAAuB;EACrE,gBAAgB,CAAC,oBAAoB,GAAG,EAAqB;EAC7D,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAgB,EACxD,gBAAgB,GACjB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAO,EAAY,WAAW,EAAY,SAC1C,IAAY,EAAuC,SAAS,EAAO,GACrE,IACA,CAAC,GAAQ,GAAG,EAAiB,EAE3B,IAAW,OAAO,MAAiB;AACvC,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAa,EAAE,WAAW;IAAE,IAAI;IAAW,OAAO,EAAE,QAAQ,GAAM;IAAE,EAAE,CAAC,CAAC;WACpF,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;IAG3E,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,EAC5D,EAAS,0BAA0B;WAC5B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,cAAW;KACX,eAAY;KACZ,WAAU;KACV,OAAO;KACP,UAAU;KACV,WAAW,MAAM;AACf,MAAI,EAAE,OAAO,UAAU,KAAa,EAAS,EAAE,OAAO,MAAM;;eAG7D,EAAS,KAAK,MACb,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAU,EAAE;MACN,EAFI,EAEJ,CACT;KACK,CAAA;IACT,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,+BAA+B,EAAU,CAAC;eAJ/E,CAME,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;IACT,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,UAAU,EAAK;KACtB,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU;KACV,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release controls (BOFF-7952). A release started in Planning and could never be released: status showed only as
|
|
3
|
+
* badges, the Edit Release page was linked from nowhere, and nothing deleted a release. The Quality Gates card even
|
|
4
|
+
* said the release "cannot be released until the gates pass" — with nothing to click once they had. The service
|
|
5
|
+
* refuses RELEASED while a gate is open; that reason is shown here.
|
|
6
|
+
*/
|
|
7
|
+
/** Every ReleaseStatus value (the Edit page's list omitted STAGING — BOFF-7985). */
|
|
8
|
+
export declare const RELEASE_STATUSES: readonly ["DRAFT", "PLANNING", "IN_PROGRESS", "READY", "STAGING", "RELEASED", "ROLLED_BACK", "CANCELLED"];
|
|
9
|
+
export declare function ReleaseHeaderActions({ releaseId, version, status, }: {
|
|
10
|
+
releaseId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
status: string;
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=ReleaseControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReleaseControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/ReleaseControls.tsx"],"names":[],"mappings":"AAWA;;;;;GAKG;AAEH,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,2GASnB,CAAC;AA0BX,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,OAAO,EACP,MAAM,GACP,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,2CA4EA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { ConfirmButton as n, ErrorLine as r } from "./ConfirmButton.js";
|
|
4
|
+
import { enumLabel as i } from "./WorkItemControls.js";
|
|
5
|
+
import { detailPath as a } from "../utils/rowNavigation.js";
|
|
6
|
+
import { useState as o } from "react";
|
|
7
|
+
import { Pencil as s, Trash2 as c } from "lucide-react";
|
|
8
|
+
import { gql as l } from "@apollo/client";
|
|
9
|
+
import { useMutation as u } from "@apollo/client/react";
|
|
10
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
11
|
+
import { Button as p } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/controls/ReleaseControls.tsx
|
|
13
|
+
var m = [
|
|
14
|
+
"DRAFT",
|
|
15
|
+
"PLANNING",
|
|
16
|
+
"IN_PROGRESS",
|
|
17
|
+
"READY",
|
|
18
|
+
"STAGING",
|
|
19
|
+
"RELEASED",
|
|
20
|
+
"ROLLED_BACK",
|
|
21
|
+
"CANCELLED"
|
|
22
|
+
], h = [
|
|
23
|
+
"GetRelease",
|
|
24
|
+
"ListReleases",
|
|
25
|
+
"ListReleasesForProduct",
|
|
26
|
+
"ListReleasesForProducts",
|
|
27
|
+
"ReleaseReadiness",
|
|
28
|
+
"GetReleaseForEdit"
|
|
29
|
+
], g = l`
|
|
30
|
+
mutation ReleaseControlsStatus($id: ID!, $input: PlanmagnetUpdateReleaseInput!) {
|
|
31
|
+
planmagnetUpdateRelease(id: $id, input: $input) {
|
|
32
|
+
id
|
|
33
|
+
status
|
|
34
|
+
releasedAt
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`, _ = l`
|
|
38
|
+
mutation ReleaseControlsDelete($id: ID!) {
|
|
39
|
+
planmagnetDeleteRelease(id: $id)
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
function v({ releaseId: l, version: v, status: y }) {
|
|
43
|
+
let b = e(), [x, S] = u(g, {
|
|
44
|
+
refetchQueries: h,
|
|
45
|
+
awaitRefetchQueries: !0
|
|
46
|
+
}), [C, w] = u(_, { refetchQueries: h.filter((e) => e !== "GetRelease" && e !== "ReleaseReadiness") }), [T, E] = o(null), D = S.loading || w.loading, O = m.includes(y) ? m : [y, ...m], k = async (e) => {
|
|
47
|
+
E(null);
|
|
48
|
+
try {
|
|
49
|
+
await t(x({ variables: {
|
|
50
|
+
id: l,
|
|
51
|
+
input: { status: e }
|
|
52
|
+
} }));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
E(e instanceof Error ? e.message : "The status could not be changed.");
|
|
55
|
+
}
|
|
56
|
+
}, A = async () => {
|
|
57
|
+
E(null);
|
|
58
|
+
try {
|
|
59
|
+
await t(C({ variables: { id: l } })), b("/products/releases-page");
|
|
60
|
+
} catch (e) {
|
|
61
|
+
E(e instanceof Error ? e.message : "The release could not be deleted.");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ f("div", {
|
|
65
|
+
className: "flex flex-col items-end gap-2",
|
|
66
|
+
children: [/* @__PURE__ */ f("div", {
|
|
67
|
+
className: "flex flex-wrap items-center justify-end gap-2",
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ d("select", {
|
|
70
|
+
"aria-label": "Release status",
|
|
71
|
+
"data-testid": "release-status",
|
|
72
|
+
className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
|
|
73
|
+
value: y,
|
|
74
|
+
disabled: D,
|
|
75
|
+
onChange: (e) => {
|
|
76
|
+
e.target.value !== y && k(e.target.value);
|
|
77
|
+
},
|
|
78
|
+
children: O.map((e) => /* @__PURE__ */ d("option", {
|
|
79
|
+
value: e,
|
|
80
|
+
children: i(e)
|
|
81
|
+
}, e))
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ f(p, {
|
|
84
|
+
size: "sm",
|
|
85
|
+
variant: "outline",
|
|
86
|
+
"data-testid": "release-edit",
|
|
87
|
+
onClick: () => b(a("/products/edit-release-page", l)),
|
|
88
|
+
children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
89
|
+
}),
|
|
90
|
+
/* @__PURE__ */ d(n, {
|
|
91
|
+
label: "Delete",
|
|
92
|
+
variant: "outline",
|
|
93
|
+
icon: /* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }),
|
|
94
|
+
title: `Delete release ${v}?`,
|
|
95
|
+
description: "The release is deleted; its work items stay. This cannot be undone here.",
|
|
96
|
+
confirmLabel: "Delete release",
|
|
97
|
+
destructive: !0,
|
|
98
|
+
disabled: D,
|
|
99
|
+
onConfirm: () => void A()
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
}), T && /* @__PURE__ */ d(r, { message: T })]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { m as RELEASE_STATUSES, v as ReleaseHeaderActions };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=ReleaseControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReleaseControls.js","names":[],"sources":["../../../src/planmagnet/controls/ReleaseControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Release controls (BOFF-7952). A release started in Planning and could never be released: status showed only as\n * badges, the Edit Release page was linked from nowhere, and nothing deleted a release. The Quality Gates card even\n * said the release \"cannot be released until the gates pass\" — with nothing to click once they had. The service\n * refuses RELEASED while a gate is open; that reason is shown here.\n */\n\n/** Every ReleaseStatus value (the Edit page's list omitted STAGING — BOFF-7985). */\nexport const RELEASE_STATUSES = [\n 'DRAFT',\n 'PLANNING',\n 'IN_PROGRESS',\n 'READY',\n 'STAGING',\n 'RELEASED',\n 'ROLLED_BACK',\n 'CANCELLED',\n] as const;\n\nconst RELEASE_QUERIES = [\n 'GetRelease',\n 'ListReleases',\n 'ListReleasesForProduct',\n 'ListReleasesForProducts',\n 'ReleaseReadiness',\n 'GetReleaseForEdit',\n];\n\nconst UPDATE = gql`\n mutation ReleaseControlsStatus($id: ID!, $input: PlanmagnetUpdateReleaseInput!) {\n planmagnetUpdateRelease(id: $id, input: $input) {\n id\n status\n releasedAt\n }\n }\n`;\nconst DELETE = gql`\n mutation ReleaseControlsDelete($id: ID!) {\n planmagnetDeleteRelease(id: $id)\n }\n`;\n\nexport function ReleaseHeaderActions({\n releaseId,\n version,\n status,\n}: {\n releaseId: string;\n version: string;\n status: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, {\n refetchQueries: RELEASE_QUERIES,\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: RELEASE_QUERIES.filter((q) => q !== 'GetRelease' && q !== 'ReleaseReadiness'),\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (RELEASE_STATUSES as readonly string[]).includes(status)\n ? RELEASE_STATUSES\n : [status, ...RELEASE_STATUSES];\n\n const onStatus = async (next: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: releaseId, input: { status: next } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: releaseId } }));\n navigate('/products/releases-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The release could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Release status\"\n data-testid=\"release-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"release-edit\"\n onClick={() => navigate(detailPath('/products/edit-release-page', releaseId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete release ${version}?`}\n description=\"The release is deleted; its work items stay. This cannot be undone here.\"\n confirmLabel=\"Delete release\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAa,IAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAS,CAAG;;;;;;;;GASZ,IAAS,CAAG;;;;;AAMlB,SAAgB,EAAqB,EACnC,cACA,YACA,aAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAQ,EAChD,gBAAgB,EAAgB,QAAQ,MAAM,MAAM,gBAAgB,MAAM,mBAAmB,EAC9F,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAO,EAAY,WAAW,EAAY,SAC1C,IAAY,EAAuC,SAAS,EAAO,GACrE,IACA,CAAC,GAAQ,GAAG,EAAiB,EAE3B,IAAW,OAAO,MAAiB;AACvC,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI;IAAW,OAAO,EAAE,QAAQ,GAAM;IAAE,EAAE,CAAC,CAAC;WAC9E,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;IAG3E,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,EAC5D,EAAS,0BAA0B;WAC5B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,cAAW;KACX,eAAY;KACZ,WAAU;KACV,OAAO;KACP,UAAU;KACV,WAAW,MAAM;AACf,MAAI,EAAE,OAAO,UAAU,KAAa,EAAS,EAAE,OAAO,MAAM;;eAG7D,EAAS,KAAK,MACb,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAU,EAAE;MACN,EAFI,EAEJ,CACT;KACK,CAAA;IACT,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,+BAA+B,EAAU,CAAC;eAJ/E,CAME,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;IACT,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,kBAAkB,EAAQ;KACjC,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU;KACV,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or
|
|
3
|
+
* assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status
|
|
4
|
+
* could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team
|
|
5
|
+
* page, sprint "Team Members" list and board card read "Unassigned".
|
|
6
|
+
*
|
|
7
|
+
* Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records
|
|
8
|
+
* its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WORK_ITEM_STATUSES: readonly ["BACKLOG", "TODO", "IN_PROGRESS", "IN_REVIEW", "TESTING", "DONE", "CANCELLED"];
|
|
11
|
+
export declare const WORK_ITEM_PRIORITIES: readonly ["NONE", "LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
12
|
+
export declare function enumLabel(value: string): string;
|
|
13
|
+
/** A status or priority select that saves on change. */
|
|
14
|
+
export declare function WorkItemEnumSelect({ workItemId, field, value, options, label, }: {
|
|
15
|
+
workItemId: string;
|
|
16
|
+
field: 'status' | 'priority';
|
|
17
|
+
value: string;
|
|
18
|
+
options: readonly string[];
|
|
19
|
+
label: string;
|
|
20
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
/**
|
|
22
|
+
* Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any
|
|
23
|
+
* member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet
|
|
24
|
+
* admin rights, so an ordinary member would get an empty picker.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useAssigneeOptions(currentId: string | null | undefined): {
|
|
27
|
+
options: {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}[];
|
|
31
|
+
loading: boolean;
|
|
32
|
+
hasMore: boolean;
|
|
33
|
+
fetchMore: () => void;
|
|
34
|
+
};
|
|
35
|
+
/** A plain assignee select (for forms). '' means unassigned. */
|
|
36
|
+
export declare function AssigneeSelect({ id, value, onChange, disabled, }: {
|
|
37
|
+
id?: string;
|
|
38
|
+
value: string;
|
|
39
|
+
onChange: (userId: string) => void;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
/** The task detail's assignee control: saves on change. */
|
|
43
|
+
export declare function WorkItemAssigneeSelect({ workItemId, assigneeId, }: {
|
|
44
|
+
workItemId: string;
|
|
45
|
+
assigneeId: string | null;
|
|
46
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
47
|
+
/** Edit and Delete for the task detail header. */
|
|
48
|
+
export declare function WorkItemHeaderActions({ workItemId, title, }: {
|
|
49
|
+
workItemId: string;
|
|
50
|
+
title: string;
|
|
51
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
//# sourceMappingURL=WorkItemControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkItemControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"names":[],"mappings":"AAWA;;;;;;;;GAQG;AAEH,eAAO,MAAM,kBAAkB,0FAQrB,CAAC;AACX,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAE3F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C;AA0CD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,EACjC,UAAU,EACV,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,2CAyBA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;EAmBtE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,2CAkCA;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,UAAU,GACX,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,2CAcA;AAED,kDAAkD;AAClD,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,2CA0CA"}
|