@burdenoff/microfe-planmagnet 2026.928.6 → 2026.928.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.d.ts +5 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +15 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.js +13 -12
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +7 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +45 -8
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +74 -59
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +153 -63
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +138 -112
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +77 -70
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
3
|
import { useEntityId as n } from "../hooks/useEntityId.js";
|
|
4
|
-
import {
|
|
5
|
-
import { enumLabel as
|
|
6
|
-
import { detailPath as
|
|
7
|
-
import { endBeforeStart as
|
|
8
|
-
import { PROJECT_STATUSES as
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { ArrowLeft as
|
|
11
|
-
import { gql as
|
|
12
|
-
import { useMutation as
|
|
13
|
-
import { jsx as
|
|
14
|
-
import { Button as
|
|
4
|
+
import { PortfolioSelect as r, ProgramSelect as i } from "../controls/Pickers.js";
|
|
5
|
+
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
6
|
+
import { detailPath as o } from "../utils/rowNavigation.js";
|
|
7
|
+
import { endBeforeStart as s } from "../utils/formValidation.js";
|
|
8
|
+
import { PROJECT_STATUSES as c } from "../controls/ProjectControls.js";
|
|
9
|
+
import { useMemo as l, useState as u } from "react";
|
|
10
|
+
import { ArrowLeft as d, Save as f } from "lucide-react";
|
|
11
|
+
import { gql as p } from "@apollo/client";
|
|
12
|
+
import { useMutation as m, useQuery as h } from "@apollo/client/react";
|
|
13
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
14
|
+
import { Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
|
|
15
15
|
//#region src/planmagnet/pages/ProjectsEditProjectPagePage.tsx
|
|
16
|
-
var
|
|
16
|
+
var w = p`
|
|
17
17
|
query GetProjectsForEdit($id: ID!) {
|
|
18
18
|
planmagnetProject(id: $id) {
|
|
19
19
|
id
|
|
@@ -27,9 +27,10 @@ var C = f`
|
|
|
27
27
|
endDate
|
|
28
28
|
tags
|
|
29
29
|
programId
|
|
30
|
+
portfolioId
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
`,
|
|
33
|
+
`, T = p`
|
|
33
34
|
mutation UpdateProject($id: ID!, $input: PlanmagnetUpdateProjectInput!) {
|
|
34
35
|
planmagnetUpdateProject(id: $id, input: $input) {
|
|
35
36
|
id
|
|
@@ -47,25 +48,31 @@ var C = f`
|
|
|
47
48
|
id
|
|
48
49
|
name
|
|
49
50
|
}
|
|
51
|
+
portfolioId
|
|
52
|
+
portfolio {
|
|
53
|
+
id
|
|
54
|
+
name
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
`;
|
|
53
|
-
function
|
|
54
|
-
let
|
|
55
|
-
variables: { id:
|
|
56
|
-
skip: !
|
|
57
|
-
}), [
|
|
58
|
-
name:
|
|
59
|
-
description:
|
|
60
|
-
methodology:
|
|
61
|
-
status:
|
|
62
|
-
startDate:
|
|
63
|
-
endDate:
|
|
64
|
-
programId:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...N
|
|
68
|
-
|
|
59
|
+
function E() {
|
|
60
|
+
let p = n("projectId"), E = e(), { data: D, loading: O, error: k } = h(w, {
|
|
61
|
+
variables: { id: p },
|
|
62
|
+
skip: !p
|
|
63
|
+
}), [A, j] = u(null), M = D?.planmagnetProject, N = l(() => ({
|
|
64
|
+
name: M?.name ?? "",
|
|
65
|
+
description: M?.description ?? "",
|
|
66
|
+
methodology: M?.methodology ?? "SCRUM",
|
|
67
|
+
status: M?.status ?? "PLANNING",
|
|
68
|
+
startDate: M?.startDate ? M.startDate.split("T")[0] : "",
|
|
69
|
+
endDate: M?.endDate ? M.endDate.split("T")[0] : "",
|
|
70
|
+
programId: M?.programId ?? "",
|
|
71
|
+
portfolioId: M?.portfolioId ?? ""
|
|
72
|
+
}), [M]), [P, F] = u({}), I = {
|
|
73
|
+
...N,
|
|
74
|
+
...P
|
|
75
|
+
}, L = (e) => F(e), [R, { loading: z }] = m(T, { refetchQueries: [
|
|
69
76
|
"GetProjects",
|
|
70
77
|
"GetActiveProjects",
|
|
71
78
|
"GetDashboardData",
|
|
@@ -73,106 +80,109 @@ function T() {
|
|
|
73
80
|
"GetProjectDetail",
|
|
74
81
|
"AdminPlanmagnetProjects",
|
|
75
82
|
"GetProgram",
|
|
76
|
-
"ListPrograms"
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
"ListPrograms",
|
|
84
|
+
"GetPortfolio",
|
|
85
|
+
"ListPortfolios"
|
|
86
|
+
] }), [B, V] = u(null), H = s(I.startDate, I.endDate);
|
|
87
|
+
return p ? O && !D ? /* @__PURE__ */ g("div", {
|
|
79
88
|
className: "flex items-center justify-center p-12",
|
|
80
|
-
children: /* @__PURE__ */
|
|
81
|
-
}) :
|
|
89
|
+
children: /* @__PURE__ */ g("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
90
|
+
}) : k ? /* @__PURE__ */ _("div", {
|
|
82
91
|
className: "p-6 text-destructive",
|
|
83
|
-
children: ["Error loading project: ",
|
|
84
|
-
}) :
|
|
92
|
+
children: ["Error loading project: ", k.message]
|
|
93
|
+
}) : M ? /* @__PURE__ */ _("div", {
|
|
85
94
|
className: "space-y-6 p-6 max-w-4xl",
|
|
86
95
|
children: [
|
|
87
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ _("div", {
|
|
88
97
|
className: "flex items-center justify-between",
|
|
89
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ _("div", {
|
|
90
99
|
className: "flex items-center gap-3",
|
|
91
|
-
children: [/* @__PURE__ */
|
|
100
|
+
children: [/* @__PURE__ */ g(v, {
|
|
92
101
|
variant: "ghost",
|
|
93
102
|
size: "sm",
|
|
94
103
|
"aria-label": "Back to the project",
|
|
95
|
-
onClick: () =>
|
|
96
|
-
children: /* @__PURE__ */
|
|
97
|
-
}), /* @__PURE__ */
|
|
104
|
+
onClick: () => E(o("/projects/project-detail-page", p)),
|
|
105
|
+
children: /* @__PURE__ */ g(d, { className: "h-4 w-4" })
|
|
106
|
+
}), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h1", {
|
|
98
107
|
className: "text-2xl font-semibold",
|
|
99
108
|
children: "Edit Project"
|
|
100
|
-
}), /* @__PURE__ */
|
|
109
|
+
}), /* @__PURE__ */ _("p", {
|
|
101
110
|
className: "text-sm text-muted-foreground",
|
|
102
|
-
children: ["Editing ",
|
|
111
|
+
children: ["Editing ", M.name]
|
|
103
112
|
})] })]
|
|
104
|
-
}), /* @__PURE__ */
|
|
113
|
+
}), /* @__PURE__ */ _(v, {
|
|
105
114
|
size: "sm",
|
|
106
115
|
onClick: async () => {
|
|
107
|
-
if (!
|
|
108
|
-
|
|
116
|
+
if (!M || H) return;
|
|
117
|
+
V(null), j(null);
|
|
109
118
|
let e = {
|
|
110
|
-
...
|
|
111
|
-
...
|
|
119
|
+
...I.startDate !== N.startDate && { startDate: I.startDate || void 0 },
|
|
120
|
+
...I.endDate !== N.endDate && { endDate: I.endDate || void 0 }
|
|
112
121
|
};
|
|
113
122
|
try {
|
|
114
|
-
await t(
|
|
115
|
-
id:
|
|
123
|
+
await t(R({ variables: {
|
|
124
|
+
id: M.id,
|
|
116
125
|
input: {
|
|
117
|
-
name:
|
|
118
|
-
description:
|
|
119
|
-
methodology:
|
|
120
|
-
...
|
|
126
|
+
name: I.name || void 0,
|
|
127
|
+
description: I.description || void 0,
|
|
128
|
+
methodology: I.methodology || void 0,
|
|
129
|
+
...I.status !== N.status && { status: I.status },
|
|
121
130
|
...e,
|
|
122
|
-
...
|
|
131
|
+
...I.programId !== N.programId && { programId: I.programId || null },
|
|
132
|
+
...I.portfolioId !== N.portfolioId && { portfolioId: I.portfolioId || null }
|
|
123
133
|
}
|
|
124
|
-
} })),
|
|
134
|
+
} })), j("Project updated successfully.");
|
|
125
135
|
} catch (e) {
|
|
126
|
-
|
|
136
|
+
V(e instanceof Error ? e.message : "The project could not be saved.");
|
|
127
137
|
}
|
|
128
138
|
},
|
|
129
|
-
disabled:
|
|
130
|
-
children: [/* @__PURE__ */
|
|
139
|
+
disabled: z || H,
|
|
140
|
+
children: [/* @__PURE__ */ g(f, { className: "mr-1 h-4 w-4" }), z ? "Saving..." : "Save Changes"]
|
|
131
141
|
})]
|
|
132
142
|
}),
|
|
133
|
-
|
|
143
|
+
A && /* @__PURE__ */ g("div", {
|
|
134
144
|
role: "status",
|
|
135
145
|
className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
|
|
136
|
-
children:
|
|
146
|
+
children: A
|
|
137
147
|
}),
|
|
138
|
-
|
|
148
|
+
B && /* @__PURE__ */ g("div", {
|
|
139
149
|
role: "alert",
|
|
140
150
|
className: "p-3 bg-destructive/10 text-destructive rounded-md text-sm",
|
|
141
|
-
children:
|
|
151
|
+
children: B
|
|
142
152
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ g(S, { children: "Project Details" }) }), /* @__PURE__ */ _(b, {
|
|
144
154
|
className: "space-y-4",
|
|
145
155
|
children: [
|
|
146
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
147
157
|
className: "text-sm font-medium",
|
|
148
158
|
children: "Project Name"
|
|
149
|
-
}), /* @__PURE__ */
|
|
150
|
-
value:
|
|
151
|
-
onChange: (e) =>
|
|
152
|
-
...
|
|
159
|
+
}), /* @__PURE__ */ g(C, {
|
|
160
|
+
value: I.name,
|
|
161
|
+
onChange: (e) => L({
|
|
162
|
+
...I,
|
|
153
163
|
name: e.target.value
|
|
154
164
|
}),
|
|
155
165
|
className: "mt-1"
|
|
156
166
|
})] }),
|
|
157
|
-
/* @__PURE__ */
|
|
167
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
158
168
|
className: "text-sm font-medium",
|
|
159
169
|
children: "Description"
|
|
160
|
-
}), /* @__PURE__ */
|
|
170
|
+
}), /* @__PURE__ */ g("textarea", {
|
|
161
171
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
|
|
162
|
-
value:
|
|
163
|
-
onChange: (e) =>
|
|
164
|
-
...
|
|
172
|
+
value: I.description,
|
|
173
|
+
onChange: (e) => L({
|
|
174
|
+
...I,
|
|
165
175
|
description: e.target.value
|
|
166
176
|
})
|
|
167
177
|
})] }),
|
|
168
|
-
/* @__PURE__ */
|
|
178
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
169
179
|
className: "text-sm font-medium",
|
|
170
180
|
children: "Methodology"
|
|
171
|
-
}), /* @__PURE__ */
|
|
181
|
+
}), /* @__PURE__ */ g("select", {
|
|
172
182
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize",
|
|
173
|
-
value:
|
|
174
|
-
onChange: (e) =>
|
|
175
|
-
...
|
|
183
|
+
value: I.methodology,
|
|
184
|
+
onChange: (e) => L({
|
|
185
|
+
...I,
|
|
176
186
|
methodology: e.target.value
|
|
177
187
|
}),
|
|
178
188
|
children: [
|
|
@@ -182,78 +192,94 @@ function T() {
|
|
|
182
192
|
"HYBRID",
|
|
183
193
|
"AGILE",
|
|
184
194
|
"CUSTOM"
|
|
185
|
-
].map((e) => /* @__PURE__ */
|
|
195
|
+
].map((e) => /* @__PURE__ */ g("option", {
|
|
186
196
|
value: e,
|
|
187
197
|
children: e.toLowerCase()
|
|
188
198
|
}, e))
|
|
189
199
|
})] }),
|
|
190
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
191
201
|
htmlFor: "project-program",
|
|
192
202
|
className: "text-sm font-medium",
|
|
193
203
|
children: "Program"
|
|
194
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ g("div", {
|
|
195
205
|
className: "mt-1",
|
|
196
|
-
children: /* @__PURE__ */
|
|
206
|
+
children: /* @__PURE__ */ g(i, {
|
|
197
207
|
id: "project-program",
|
|
198
|
-
value:
|
|
199
|
-
onChange: (e) =>
|
|
200
|
-
...
|
|
208
|
+
value: I.programId,
|
|
209
|
+
onChange: (e) => L({
|
|
210
|
+
...I,
|
|
201
211
|
programId: e
|
|
202
212
|
}),
|
|
203
213
|
noneLabel: "No program"
|
|
204
214
|
})
|
|
205
215
|
})] }),
|
|
206
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
217
|
+
htmlFor: "project-portfolio",
|
|
218
|
+
className: "text-sm font-medium",
|
|
219
|
+
children: "Portfolio"
|
|
220
|
+
}), /* @__PURE__ */ g("div", {
|
|
221
|
+
className: "mt-1",
|
|
222
|
+
children: /* @__PURE__ */ g(r, {
|
|
223
|
+
id: "project-portfolio",
|
|
224
|
+
value: I.portfolioId,
|
|
225
|
+
onChange: (e) => L({
|
|
226
|
+
...I,
|
|
227
|
+
portfolioId: e
|
|
228
|
+
}),
|
|
229
|
+
noneLabel: "No portfolio"
|
|
230
|
+
})
|
|
231
|
+
})] }),
|
|
232
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
207
233
|
htmlFor: "project-status",
|
|
208
234
|
className: "text-sm font-medium",
|
|
209
235
|
children: "Status"
|
|
210
|
-
}), /* @__PURE__ */
|
|
236
|
+
}), /* @__PURE__ */ g("select", {
|
|
211
237
|
id: "project-status",
|
|
212
238
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
213
|
-
value:
|
|
214
|
-
onChange: (e) =>
|
|
215
|
-
...
|
|
239
|
+
value: I.status,
|
|
240
|
+
onChange: (e) => L({
|
|
241
|
+
...I,
|
|
216
242
|
status: e.target.value
|
|
217
243
|
}),
|
|
218
|
-
children: (
|
|
244
|
+
children: (c.includes(I.status) ? c : [I.status, ...c]).map((e) => /* @__PURE__ */ g("option", {
|
|
219
245
|
value: e,
|
|
220
|
-
children:
|
|
246
|
+
children: a(e)
|
|
221
247
|
}, e))
|
|
222
248
|
})] }),
|
|
223
|
-
/* @__PURE__ */
|
|
249
|
+
/* @__PURE__ */ _("div", {
|
|
224
250
|
className: "grid grid-cols-2 gap-4",
|
|
225
|
-
children: [/* @__PURE__ */
|
|
251
|
+
children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
226
252
|
htmlFor: "project-start",
|
|
227
253
|
className: "text-sm font-medium",
|
|
228
254
|
children: "Start Date"
|
|
229
|
-
}), /* @__PURE__ */
|
|
255
|
+
}), /* @__PURE__ */ g(C, {
|
|
230
256
|
id: "project-start",
|
|
231
257
|
type: "date",
|
|
232
|
-
value:
|
|
233
|
-
max:
|
|
234
|
-
onChange: (e) =>
|
|
235
|
-
...
|
|
258
|
+
value: I.startDate,
|
|
259
|
+
max: I.endDate || void 0,
|
|
260
|
+
onChange: (e) => L({
|
|
261
|
+
...I,
|
|
236
262
|
startDate: e.target.value
|
|
237
263
|
}),
|
|
238
264
|
className: "mt-1"
|
|
239
|
-
})] }), /* @__PURE__ */
|
|
265
|
+
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
240
266
|
htmlFor: "project-end",
|
|
241
267
|
className: "text-sm font-medium",
|
|
242
268
|
children: "End Date"
|
|
243
|
-
}), /* @__PURE__ */
|
|
269
|
+
}), /* @__PURE__ */ g(C, {
|
|
244
270
|
id: "project-end",
|
|
245
271
|
type: "date",
|
|
246
|
-
value:
|
|
247
|
-
min:
|
|
248
|
-
onChange: (e) =>
|
|
249
|
-
...
|
|
272
|
+
value: I.endDate,
|
|
273
|
+
min: I.startDate || void 0,
|
|
274
|
+
onChange: (e) => L({
|
|
275
|
+
...I,
|
|
250
276
|
endDate: e.target.value
|
|
251
277
|
}),
|
|
252
|
-
"aria-invalid":
|
|
278
|
+
"aria-invalid": H,
|
|
253
279
|
className: "mt-1"
|
|
254
280
|
})] })]
|
|
255
281
|
}),
|
|
256
|
-
|
|
282
|
+
H && /* @__PURE__ */ g("p", {
|
|
257
283
|
role: "alert",
|
|
258
284
|
className: "text-sm text-destructive",
|
|
259
285
|
children: "The end date is before the start date."
|
|
@@ -261,15 +287,15 @@ function T() {
|
|
|
261
287
|
]
|
|
262
288
|
})] })
|
|
263
289
|
]
|
|
264
|
-
}) : /* @__PURE__ */
|
|
290
|
+
}) : /* @__PURE__ */ g("div", {
|
|
265
291
|
className: "p-6 text-muted-foreground",
|
|
266
292
|
children: "Project not found."
|
|
267
|
-
}) : /* @__PURE__ */
|
|
293
|
+
}) : /* @__PURE__ */ g("div", {
|
|
268
294
|
className: "p-6 text-muted-foreground",
|
|
269
295
|
children: "No project selected. Open one from the list."
|
|
270
296
|
});
|
|
271
297
|
}
|
|
272
298
|
//#endregion
|
|
273
|
-
export {
|
|
299
|
+
export { E as ProjectsEditProjectPagePage };
|
|
274
300
|
|
|
275
301
|
//# sourceMappingURL=ProjectsEditProjectPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsEditProjectPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsEditProjectPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { PROJECT_STATUSES } from '../controls/ProjectControls';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ProgramSelect } from '../controls/Pickers';\n\nconst GET_PROJECTS = gql`\n query GetProjectsForEdit($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n key\n description\n status\n methodology\n ownerId\n startDate\n endDate\n tags\n programId\n }\n }\n`;\n\nconst UPDATE_PROJECT = gql`\n mutation UpdateProject($id: ID!, $input: PlanmagnetUpdateProjectInput!) {\n planmagnetUpdateProject(id: $id, input: $input) {\n id\n name\n key\n description\n status\n methodology\n ownerId\n startDate\n endDate\n tags\n programId\n program {\n id\n name\n }\n }\n }\n`;\n\nexport function ProjectsEditProjectPagePage() {\n const projectId = useEntityId('projectId');\n const navigate = usePlanMagnetNavigate();\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_PROJECTS, {\n variables: { id: projectId },\n skip: !projectId,\n });\n\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n const project = data?.planmagnetProject;\n\n const defaultForm = useMemo(\n () => ({\n name: project?.name ?? '',\n description: project?.description ?? '',\n methodology: project?.methodology ?? 'SCRUM',\n status: project?.status ?? 'PLANNING',\n startDate: project?.startDate ? project.startDate.split('T')[0] : '',\n endDate: project?.endDate ? project.endDate.split('T')[0] : '',\n programId: project?.programId ?? '',\n }),\n [project]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (updates: Record<string, string>) => setFormOverrides(updates);\n\n // Success only after the service accepted the change (errorPolicy 'all' resolves refused mutations).\n const [updateProject, { loading: saving }] = useMutation(UPDATE_PROJECT, {\n refetchQueries: [\n 'GetProjects',\n 'GetActiveProjects',\n 'GetDashboardData',\n 'GetProjectsForEdit',\n 'GetProjectDetail',\n 'AdminPlanmagnetProjects',\n 'GetProgram',\n 'ListPrograms',\n ],\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n\n const methodologies = ['SCRUM', 'KANBAN', 'WATERFALL', 'HYBRID', 'AGILE', 'CUSTOM'] as const;\n\n const handleSave = async () => {\n if (!project || invalidDates) return;\n setSaveError(null);\n setSuccessMessage(null);\n // Dates are sent only when they were changed: the service checks the pair the project would end up\n // with, so re-sending untouched dates of an older project stored with inverted dates would refuse a\n // plain rename (BOFF-7833).\n const dateChanges = {\n ...(form.startDate !== defaultForm.startDate && { startDate: form.startDate || undefined }),\n ...(form.endDate !== defaultForm.endDate && { endDate: form.endDate || undefined }),\n };\n try {\n await throwOnError(\n updateProject({\n variables: {\n id: project.id,\n input: {\n name: form.name || undefined,\n description: form.description || undefined,\n methodology: form.methodology || undefined,\n // Only when changed: a status set elsewhere meanwhile (the detail page's select) is not overwritten.\n ...(form.status !== defaultForm.status && { status: form.status }),\n ...dateChanges,\n // Only when changed: '' clears it, so an unattached project stays unattached rather than being\n // re-sent as undefined (which the service would leave alone, not clear).\n ...(form.programId !== defaultForm.programId && {\n programId: form.programId || null,\n }),\n },\n },\n })\n );\n setSuccessMessage('Project updated successfully.');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The project could not be saved.');\n }\n };\n\n if (!projectId)\n return (\n <div className=\"p-6 text-muted-foreground\">No project selected. Open one from the list.</div>\n );\n // Only before the first load: a refetch must not blank the page (it\n // unmounts every component on it and loses their state).\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 if (error)\n return <div className=\"p-6 text-destructive\">Error loading project: {error.message}</div>;\n if (!project) return <div className=\"p-6 text-muted-foreground\">Project not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Back to the project\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', projectId))}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Project</h1>\n <p className=\"text-sm text-muted-foreground\">Editing {project.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving || invalidDates}>\n <Save className=\"mr-1 h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n\n {successMessage && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n {successMessage}\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 bg-destructive/10 text-destructive rounded-md text-sm\">\n {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Project Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Project Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Methodology</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n >\n {methodologies.map((m) => (\n <option key={m} value={m}>\n {m.toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n {/* BOFF-8047: attach or detach this project's program. Programs were `api-only` before this. */}\n <label htmlFor=\"project-program\" className=\"text-sm font-medium\">\n Program\n </label>\n <div className=\"mt-1\">\n <ProgramSelect\n id=\"project-program\"\n value={form.programId}\n onChange={(programId) => setForm({ ...form, programId })}\n noneLabel=\"No program\"\n />\n </div>\n </div>\n <div>\n {/* BOFF-7940: the form had no Status, so a project could never leave PLANNING. */}\n <label htmlFor=\"project-status\" className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id=\"project-status\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {((PROJECT_STATUSES as readonly string[]).includes(form.status)\n ? PROJECT_STATUSES\n : [form.status, ...PROJECT_STATUSES]\n ).map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"project-start\" className=\"text-sm font-medium\">\n Start Date\n </label>\n <Input\n id=\"project-start\"\n type=\"date\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label htmlFor=\"project-end\" className=\"text-sm font-medium\">\n End Date\n </label>\n <Input\n id=\"project-end\"\n type=\"date\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n aria-invalid={invalidDates}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidDates && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The end date is before the start date.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectsEditProjectPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsEditProjectPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { PROJECT_STATUSES } from '../controls/ProjectControls';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ProgramSelect, PortfolioSelect } from '../controls/Pickers';\n\nconst GET_PROJECTS = gql`\n query GetProjectsForEdit($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n key\n description\n status\n methodology\n ownerId\n startDate\n endDate\n tags\n programId\n portfolioId\n }\n }\n`;\n\nconst UPDATE_PROJECT = gql`\n mutation UpdateProject($id: ID!, $input: PlanmagnetUpdateProjectInput!) {\n planmagnetUpdateProject(id: $id, input: $input) {\n id\n name\n key\n description\n status\n methodology\n ownerId\n startDate\n endDate\n tags\n programId\n program {\n id\n name\n }\n portfolioId\n portfolio {\n id\n name\n }\n }\n }\n`;\n\nexport function ProjectsEditProjectPagePage() {\n const projectId = useEntityId('projectId');\n const navigate = usePlanMagnetNavigate();\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_PROJECTS, {\n variables: { id: projectId },\n skip: !projectId,\n });\n\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n const project = data?.planmagnetProject;\n\n const defaultForm = useMemo(\n () => ({\n name: project?.name ?? '',\n description: project?.description ?? '',\n methodology: project?.methodology ?? 'SCRUM',\n status: project?.status ?? 'PLANNING',\n startDate: project?.startDate ? project.startDate.split('T')[0] : '',\n endDate: project?.endDate ? project.endDate.split('T')[0] : '',\n programId: project?.programId ?? '',\n portfolioId: project?.portfolioId ?? '',\n }),\n [project]\n );\n\n const [formOverrides, setFormOverrides] = useState<Record<string, string>>({});\n const form = { ...defaultForm, ...formOverrides };\n const setForm = (updates: Record<string, string>) => setFormOverrides(updates);\n\n // Success only after the service accepted the change (errorPolicy 'all' resolves refused mutations).\n const [updateProject, { loading: saving }] = useMutation(UPDATE_PROJECT, {\n refetchQueries: [\n 'GetProjects',\n 'GetActiveProjects',\n 'GetDashboardData',\n 'GetProjectsForEdit',\n 'GetProjectDetail',\n 'AdminPlanmagnetProjects',\n 'GetProgram',\n 'ListPrograms',\n 'GetPortfolio',\n 'ListPortfolios',\n ],\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n\n const methodologies = ['SCRUM', 'KANBAN', 'WATERFALL', 'HYBRID', 'AGILE', 'CUSTOM'] as const;\n\n const handleSave = async () => {\n if (!project || invalidDates) return;\n setSaveError(null);\n setSuccessMessage(null);\n // Dates are sent only when they were changed: the service checks the pair the project would end up\n // with, so re-sending untouched dates of an older project stored with inverted dates would refuse a\n // plain rename (BOFF-7833).\n const dateChanges = {\n ...(form.startDate !== defaultForm.startDate && { startDate: form.startDate || undefined }),\n ...(form.endDate !== defaultForm.endDate && { endDate: form.endDate || undefined }),\n };\n try {\n await throwOnError(\n updateProject({\n variables: {\n id: project.id,\n input: {\n name: form.name || undefined,\n description: form.description || undefined,\n methodology: form.methodology || undefined,\n // Only when changed: a status set elsewhere meanwhile (the detail page's select) is not overwritten.\n ...(form.status !== defaultForm.status && { status: form.status }),\n ...dateChanges,\n // Only when changed: '' clears it, so an unattached project stays unattached rather than being\n // re-sent as undefined (which the service would leave alone, not clear).\n ...(form.programId !== defaultForm.programId && {\n programId: form.programId || null,\n }),\n // Same rule for the portfolio: only sent when changed, '' clears it (BOFF-8047).\n ...(form.portfolioId !== defaultForm.portfolioId && {\n portfolioId: form.portfolioId || null,\n }),\n },\n },\n })\n );\n setSuccessMessage('Project updated successfully.');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The project could not be saved.');\n }\n };\n\n if (!projectId)\n return (\n <div className=\"p-6 text-muted-foreground\">No project selected. Open one from the list.</div>\n );\n // Only before the first load: a refetch must not blank the page (it\n // unmounts every component on it and loses their state).\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 if (error)\n return <div className=\"p-6 text-destructive\">Error loading project: {error.message}</div>;\n if (!project) return <div className=\"p-6 text-muted-foreground\">Project not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Back to the project\"\n onClick={() => navigate(detailPath('/projects/project-detail-page', projectId))}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Project</h1>\n <p className=\"text-sm text-muted-foreground\">Editing {project.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving || invalidDates}>\n <Save className=\"mr-1 h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n\n {successMessage && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n {successMessage}\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 bg-destructive/10 text-destructive rounded-md text-sm\">\n {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Project Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Project Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Methodology</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n >\n {methodologies.map((m) => (\n <option key={m} value={m}>\n {m.toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n {/* BOFF-8047: attach or detach this project's program. Programs were `api-only` before this. */}\n <label htmlFor=\"project-program\" className=\"text-sm font-medium\">\n Program\n </label>\n <div className=\"mt-1\">\n <ProgramSelect\n id=\"project-program\"\n value={form.programId}\n onChange={(programId) => setForm({ ...form, programId })}\n noneLabel=\"No program\"\n />\n </div>\n </div>\n <div>\n {/* BOFF-8047: attach or detach this project's portfolio. A user could not do this from the UI before. */}\n <label htmlFor=\"project-portfolio\" className=\"text-sm font-medium\">\n Portfolio\n </label>\n <div className=\"mt-1\">\n <PortfolioSelect\n id=\"project-portfolio\"\n value={form.portfolioId}\n onChange={(portfolioId) => setForm({ ...form, portfolioId })}\n noneLabel=\"No portfolio\"\n />\n </div>\n </div>\n <div>\n {/* BOFF-7940: the form had no Status, so a project could never leave PLANNING. */}\n <label htmlFor=\"project-status\" className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id=\"project-status\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {((PROJECT_STATUSES as readonly string[]).includes(form.status)\n ? PROJECT_STATUSES\n : [form.status, ...PROJECT_STATUSES]\n ).map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"project-start\" className=\"text-sm font-medium\">\n Start Date\n </label>\n <Input\n id=\"project-start\"\n type=\"date\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label htmlFor=\"project-end\" className=\"text-sm font-medium\">\n End Date\n </label>\n <Input\n id=\"project-end\"\n type=\"date\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n aria-invalid={invalidDates}\n className=\"mt-1\"\n />\n </div>\n </div>\n {invalidDates && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n The end date is before the start date.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TemplatesTemplatesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TemplatesTemplatesPagePage.tsx"],"names":[],"mappings":"AA2DA,wBAAgB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"TemplatesTemplatesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TemplatesTemplatesPagePage.tsx"],"names":[],"mappings":"AA2DA,wBAAgB,0BAA0B,4CAqMzC"}
|