@burdenoff/microfe-planmagnet 2026.928.2 → 2026.928.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/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +102 -84
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +103 -55
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/controls/CompetitorControls.d.ts +20 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +172 -0
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +29 -29
- package/dist/planmagnet/controls/FeatureTriage.js +11 -11
- package/dist/planmagnet/controls/ObjectiveControls.js +24 -24
- package/dist/planmagnet/controls/Pickers.d.ts +17 -2
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +100 -9
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.d.ts +13 -0
- package/dist/planmagnet/controls/PortfolioControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/PortfolioControls.js +114 -0
- package/dist/planmagnet/controls/PortfolioControls.js.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts +25 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.js +187 -0
- package/dist/planmagnet/controls/ProgramControls.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +127 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +129 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +132 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +181 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js +242 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +302 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +335 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +154 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +124 -138
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +221 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +279 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js +178 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +157 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +130 -103
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +156 -136
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksImportPagePage.js +5 -5
- package/dist/planmagnet/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +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 { detailPath as n } from "../utils/rowNavigation.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { ProgramSelect as r } from "../controls/Pickers.js";
|
|
5
|
+
import { endBeforeStart as i } from "../utils/formValidation.js";
|
|
6
|
+
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
7
7
|
import { PROJECT_STATUSES as o } from "../controls/ProjectControls.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { useEntityId as s } from "../hooks/useEntityId.js";
|
|
9
|
+
import { useMemo as c, useState as l } from "react";
|
|
10
|
+
import { ArrowLeft as u, Save as d } from "lucide-react";
|
|
11
|
+
import { gql as f } from "@apollo/client";
|
|
12
|
+
import { useMutation as p, useQuery as m } from "@apollo/client/react";
|
|
13
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
14
|
+
import { Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, Input as S } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/planmagnet/pages/ProjectsEditProjectPagePage.tsx
|
|
15
|
-
var
|
|
16
|
+
var C = f`
|
|
16
17
|
query GetProjectsForEdit($id: ID!) {
|
|
17
18
|
planmagnetProject(id: $id) {
|
|
18
19
|
id
|
|
@@ -25,9 +26,10 @@ var S = d`
|
|
|
25
26
|
startDate
|
|
26
27
|
endDate
|
|
27
28
|
tags
|
|
29
|
+
programId
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
`,
|
|
32
|
+
`, w = f`
|
|
31
33
|
mutation UpdateProject($id: ID!, $input: PlanmagnetUpdateProjectInput!) {
|
|
32
34
|
planmagnetUpdateProject(id: $id, input: $input) {
|
|
33
35
|
id
|
|
@@ -40,128 +42,137 @@ var S = d`
|
|
|
40
42
|
startDate
|
|
41
43
|
endDate
|
|
42
44
|
tags
|
|
45
|
+
programId
|
|
46
|
+
program {
|
|
47
|
+
id
|
|
48
|
+
name
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
`;
|
|
46
|
-
function
|
|
47
|
-
let
|
|
48
|
-
variables: { id:
|
|
49
|
-
skip: !
|
|
50
|
-
}), [
|
|
51
|
-
name:
|
|
52
|
-
description:
|
|
53
|
-
methodology:
|
|
54
|
-
status:
|
|
55
|
-
startDate:
|
|
56
|
-
endDate:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...M
|
|
60
|
-
|
|
53
|
+
function T() {
|
|
54
|
+
let f = s("projectId"), T = e(), { data: E, loading: D, error: O } = m(C, {
|
|
55
|
+
variables: { id: f },
|
|
56
|
+
skip: !f
|
|
57
|
+
}), [k, A] = l(null), j = E?.planmagnetProject, M = c(() => ({
|
|
58
|
+
name: j?.name ?? "",
|
|
59
|
+
description: j?.description ?? "",
|
|
60
|
+
methodology: j?.methodology ?? "SCRUM",
|
|
61
|
+
status: j?.status ?? "PLANNING",
|
|
62
|
+
startDate: j?.startDate ? j.startDate.split("T")[0] : "",
|
|
63
|
+
endDate: j?.endDate ? j.endDate.split("T")[0] : "",
|
|
64
|
+
programId: j?.programId ?? ""
|
|
65
|
+
}), [j]), [N, P] = l({}), F = {
|
|
66
|
+
...M,
|
|
67
|
+
...N
|
|
68
|
+
}, I = (e) => P(e), [L, { loading: R }] = p(w, { refetchQueries: [
|
|
61
69
|
"GetProjects",
|
|
62
70
|
"GetActiveProjects",
|
|
63
71
|
"GetDashboardData",
|
|
64
72
|
"GetProjectsForEdit",
|
|
65
73
|
"GetProjectDetail",
|
|
66
|
-
"AdminPlanmagnetProjects"
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
"AdminPlanmagnetProjects",
|
|
75
|
+
"GetProgram",
|
|
76
|
+
"ListPrograms"
|
|
77
|
+
] }), [z, B] = l(null), V = i(F.startDate, F.endDate);
|
|
78
|
+
return f ? D && !E ? /* @__PURE__ */ h("div", {
|
|
69
79
|
className: "flex items-center justify-center p-12",
|
|
70
|
-
children: /* @__PURE__ */
|
|
71
|
-
}) :
|
|
80
|
+
children: /* @__PURE__ */ h("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
81
|
+
}) : O ? /* @__PURE__ */ g("div", {
|
|
72
82
|
className: "p-6 text-destructive",
|
|
73
|
-
children: ["Error loading project: ",
|
|
74
|
-
}) :
|
|
83
|
+
children: ["Error loading project: ", O.message]
|
|
84
|
+
}) : j ? /* @__PURE__ */ g("div", {
|
|
75
85
|
className: "space-y-6 p-6 max-w-4xl",
|
|
76
86
|
children: [
|
|
77
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ g("div", {
|
|
78
88
|
className: "flex items-center justify-between",
|
|
79
|
-
children: [/* @__PURE__ */
|
|
89
|
+
children: [/* @__PURE__ */ g("div", {
|
|
80
90
|
className: "flex items-center gap-3",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
91
|
+
children: [/* @__PURE__ */ h(_, {
|
|
82
92
|
variant: "ghost",
|
|
83
93
|
size: "sm",
|
|
84
94
|
"aria-label": "Back to the project",
|
|
85
|
-
onClick: () =>
|
|
86
|
-
children: /* @__PURE__ */
|
|
87
|
-
}), /* @__PURE__ */
|
|
95
|
+
onClick: () => T(n("/projects/project-detail-page", f)),
|
|
96
|
+
children: /* @__PURE__ */ h(u, { className: "h-4 w-4" })
|
|
97
|
+
}), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
|
|
88
98
|
className: "text-2xl font-semibold",
|
|
89
99
|
children: "Edit Project"
|
|
90
|
-
}), /* @__PURE__ */
|
|
100
|
+
}), /* @__PURE__ */ g("p", {
|
|
91
101
|
className: "text-sm text-muted-foreground",
|
|
92
|
-
children: ["Editing ",
|
|
102
|
+
children: ["Editing ", j.name]
|
|
93
103
|
})] })]
|
|
94
|
-
}), /* @__PURE__ */
|
|
104
|
+
}), /* @__PURE__ */ g(_, {
|
|
95
105
|
size: "sm",
|
|
96
106
|
onClick: async () => {
|
|
97
|
-
if (!
|
|
98
|
-
|
|
107
|
+
if (!j || V) return;
|
|
108
|
+
B(null), A(null);
|
|
99
109
|
let e = {
|
|
100
|
-
...
|
|
101
|
-
...
|
|
110
|
+
...F.startDate !== M.startDate && { startDate: F.startDate || void 0 },
|
|
111
|
+
...F.endDate !== M.endDate && { endDate: F.endDate || void 0 }
|
|
102
112
|
};
|
|
103
113
|
try {
|
|
104
|
-
await t(
|
|
105
|
-
id:
|
|
114
|
+
await t(L({ variables: {
|
|
115
|
+
id: j.id,
|
|
106
116
|
input: {
|
|
107
|
-
name:
|
|
108
|
-
description:
|
|
109
|
-
methodology:
|
|
110
|
-
...
|
|
111
|
-
...e
|
|
117
|
+
name: F.name || void 0,
|
|
118
|
+
description: F.description || void 0,
|
|
119
|
+
methodology: F.methodology || void 0,
|
|
120
|
+
...F.status !== M.status && { status: F.status },
|
|
121
|
+
...e,
|
|
122
|
+
...F.programId !== M.programId && { programId: F.programId || null }
|
|
112
123
|
}
|
|
113
|
-
} })),
|
|
124
|
+
} })), A("Project updated successfully.");
|
|
114
125
|
} catch (e) {
|
|
115
|
-
|
|
126
|
+
B(e instanceof Error ? e.message : "The project could not be saved.");
|
|
116
127
|
}
|
|
117
128
|
},
|
|
118
|
-
disabled:
|
|
119
|
-
children: [/* @__PURE__ */
|
|
129
|
+
disabled: R || V,
|
|
130
|
+
children: [/* @__PURE__ */ h(d, { className: "mr-1 h-4 w-4" }), R ? "Saving..." : "Save Changes"]
|
|
120
131
|
})]
|
|
121
132
|
}),
|
|
122
|
-
|
|
133
|
+
k && /* @__PURE__ */ h("div", {
|
|
123
134
|
role: "status",
|
|
124
135
|
className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
|
|
125
|
-
children:
|
|
136
|
+
children: k
|
|
126
137
|
}),
|
|
127
|
-
|
|
138
|
+
z && /* @__PURE__ */ h("div", {
|
|
128
139
|
role: "alert",
|
|
129
140
|
className: "p-3 bg-destructive/10 text-destructive rounded-md text-sm",
|
|
130
|
-
children:
|
|
141
|
+
children: z
|
|
131
142
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
143
|
+
/* @__PURE__ */ g(v, { children: [/* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(x, { children: "Project Details" }) }), /* @__PURE__ */ g(y, {
|
|
133
144
|
className: "space-y-4",
|
|
134
145
|
children: [
|
|
135
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
136
147
|
className: "text-sm font-medium",
|
|
137
148
|
children: "Project Name"
|
|
138
|
-
}), /* @__PURE__ */
|
|
139
|
-
value:
|
|
140
|
-
onChange: (e) =>
|
|
141
|
-
...
|
|
149
|
+
}), /* @__PURE__ */ h(S, {
|
|
150
|
+
value: F.name,
|
|
151
|
+
onChange: (e) => I({
|
|
152
|
+
...F,
|
|
142
153
|
name: e.target.value
|
|
143
154
|
}),
|
|
144
155
|
className: "mt-1"
|
|
145
156
|
})] }),
|
|
146
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
147
158
|
className: "text-sm font-medium",
|
|
148
159
|
children: "Description"
|
|
149
|
-
}), /* @__PURE__ */
|
|
160
|
+
}), /* @__PURE__ */ h("textarea", {
|
|
150
161
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
|
|
151
|
-
value:
|
|
152
|
-
onChange: (e) =>
|
|
153
|
-
...
|
|
162
|
+
value: F.description,
|
|
163
|
+
onChange: (e) => I({
|
|
164
|
+
...F,
|
|
154
165
|
description: e.target.value
|
|
155
166
|
})
|
|
156
167
|
})] }),
|
|
157
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
158
169
|
className: "text-sm font-medium",
|
|
159
170
|
children: "Methodology"
|
|
160
|
-
}), /* @__PURE__ */
|
|
171
|
+
}), /* @__PURE__ */ h("select", {
|
|
161
172
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize",
|
|
162
|
-
value:
|
|
163
|
-
onChange: (e) =>
|
|
164
|
-
...
|
|
173
|
+
value: F.methodology,
|
|
174
|
+
onChange: (e) => I({
|
|
175
|
+
...F,
|
|
165
176
|
methodology: e.target.value
|
|
166
177
|
}),
|
|
167
178
|
children: [
|
|
@@ -171,62 +182,78 @@ function w() {
|
|
|
171
182
|
"HYBRID",
|
|
172
183
|
"AGILE",
|
|
173
184
|
"CUSTOM"
|
|
174
|
-
].map((e) => /* @__PURE__ */
|
|
185
|
+
].map((e) => /* @__PURE__ */ h("option", {
|
|
175
186
|
value: e,
|
|
176
187
|
children: e.toLowerCase()
|
|
177
188
|
}, e))
|
|
178
189
|
})] }),
|
|
179
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
191
|
+
htmlFor: "project-program",
|
|
192
|
+
className: "text-sm font-medium",
|
|
193
|
+
children: "Program"
|
|
194
|
+
}), /* @__PURE__ */ h("div", {
|
|
195
|
+
className: "mt-1",
|
|
196
|
+
children: /* @__PURE__ */ h(r, {
|
|
197
|
+
id: "project-program",
|
|
198
|
+
value: F.programId,
|
|
199
|
+
onChange: (e) => I({
|
|
200
|
+
...F,
|
|
201
|
+
programId: e
|
|
202
|
+
}),
|
|
203
|
+
noneLabel: "No program"
|
|
204
|
+
})
|
|
205
|
+
})] }),
|
|
206
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
180
207
|
htmlFor: "project-status",
|
|
181
208
|
className: "text-sm font-medium",
|
|
182
209
|
children: "Status"
|
|
183
|
-
}), /* @__PURE__ */
|
|
210
|
+
}), /* @__PURE__ */ h("select", {
|
|
184
211
|
id: "project-status",
|
|
185
212
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
186
|
-
value:
|
|
187
|
-
onChange: (e) =>
|
|
188
|
-
...
|
|
213
|
+
value: F.status,
|
|
214
|
+
onChange: (e) => I({
|
|
215
|
+
...F,
|
|
189
216
|
status: e.target.value
|
|
190
217
|
}),
|
|
191
|
-
children: (o.includes(
|
|
218
|
+
children: (o.includes(F.status) ? o : [F.status, ...o]).map((e) => /* @__PURE__ */ h("option", {
|
|
192
219
|
value: e,
|
|
193
|
-
children:
|
|
220
|
+
children: a(e)
|
|
194
221
|
}, e))
|
|
195
222
|
})] }),
|
|
196
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ g("div", {
|
|
197
224
|
className: "grid grid-cols-2 gap-4",
|
|
198
|
-
children: [/* @__PURE__ */
|
|
225
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
199
226
|
htmlFor: "project-start",
|
|
200
227
|
className: "text-sm font-medium",
|
|
201
228
|
children: "Start Date"
|
|
202
|
-
}), /* @__PURE__ */
|
|
229
|
+
}), /* @__PURE__ */ h(S, {
|
|
203
230
|
id: "project-start",
|
|
204
231
|
type: "date",
|
|
205
|
-
value:
|
|
206
|
-
max:
|
|
207
|
-
onChange: (e) =>
|
|
208
|
-
...
|
|
232
|
+
value: F.startDate,
|
|
233
|
+
max: F.endDate || void 0,
|
|
234
|
+
onChange: (e) => I({
|
|
235
|
+
...F,
|
|
209
236
|
startDate: e.target.value
|
|
210
237
|
}),
|
|
211
238
|
className: "mt-1"
|
|
212
|
-
})] }), /* @__PURE__ */
|
|
239
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
213
240
|
htmlFor: "project-end",
|
|
214
241
|
className: "text-sm font-medium",
|
|
215
242
|
children: "End Date"
|
|
216
|
-
}), /* @__PURE__ */
|
|
243
|
+
}), /* @__PURE__ */ h(S, {
|
|
217
244
|
id: "project-end",
|
|
218
245
|
type: "date",
|
|
219
|
-
value:
|
|
220
|
-
min:
|
|
221
|
-
onChange: (e) =>
|
|
222
|
-
...
|
|
246
|
+
value: F.endDate,
|
|
247
|
+
min: F.startDate || void 0,
|
|
248
|
+
onChange: (e) => I({
|
|
249
|
+
...F,
|
|
223
250
|
endDate: e.target.value
|
|
224
251
|
}),
|
|
225
|
-
"aria-invalid":
|
|
252
|
+
"aria-invalid": V,
|
|
226
253
|
className: "mt-1"
|
|
227
254
|
})] })]
|
|
228
255
|
}),
|
|
229
|
-
|
|
256
|
+
V && /* @__PURE__ */ h("p", {
|
|
230
257
|
role: "alert",
|
|
231
258
|
className: "text-sm text-destructive",
|
|
232
259
|
children: "The end date is before the start date."
|
|
@@ -234,15 +261,15 @@ function w() {
|
|
|
234
261
|
]
|
|
235
262
|
})] })
|
|
236
263
|
]
|
|
237
|
-
}) : /* @__PURE__ */
|
|
264
|
+
}) : /* @__PURE__ */ h("div", {
|
|
238
265
|
className: "p-6 text-muted-foreground",
|
|
239
266
|
children: "Project not found."
|
|
240
|
-
}) : /* @__PURE__ */
|
|
267
|
+
}) : /* @__PURE__ */ h("div", {
|
|
241
268
|
className: "p-6 text-muted-foreground",
|
|
242
269
|
children: "No project selected. Open one from the list."
|
|
243
270
|
});
|
|
244
271
|
}
|
|
245
272
|
//#endregion
|
|
246
|
-
export {
|
|
273
|
+
export { T as ProjectsEditProjectPagePage };
|
|
247
274
|
|
|
248
275
|
//# 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';\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 }\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 }\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 }),\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 ],\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 },\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-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 } 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsProjectDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsProjectDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsProjectDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsProjectDetailPagePage.tsx"],"names":[],"mappings":"AAgFA,wBAAgB,6BAA6B,4CAwW5C"}
|