@burdenoff/microfe-planmagnet 2026.924.5 → 2026.925.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"AAyDA,wBAAgB,8BAA8B,4CA8O7C"}
1
+ {"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"AA+DA,wBAAgB,8BAA8B,4CAgQ7C"}
@@ -1,17 +1,22 @@
1
1
  import { PersonName as e } from "../people/People.js";
2
2
  import { useEntityId as t } from "../hooks/useEntityId.js";
3
- import { BarChart3 as n, CalendarClock as r, FolderKanban as i, Users as a } from "lucide-react";
4
- import { gql as o } from "@apollo/client";
5
- import { useQuery as s } from "@apollo/client/react";
6
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
- import { Badge as u, Card as d, CardContent as f, CardHeader as p, CardTitle as m } from "@burdenoff/fe-libs/ui";
3
+ import { SprintLifecycleActions as n, SprintWorkItemsPanel as r } from "../sprints/SprintControls.js";
4
+ import { BarChart3 as i, CalendarClock as a, FolderKanban as o, Users as s } from "lucide-react";
5
+ import { gql as c } from "@apollo/client";
6
+ import { useQuery as l } from "@apollo/client/react";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
+ import { Badge as f, Card as p, CardContent as m, CardHeader as h, CardTitle as g } from "@burdenoff/fe-libs/ui";
8
9
  //#region src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx
9
- var h = o`
10
+ var _ = c`
10
11
  query GetSprintDetail($id: ID!) {
11
12
  planmagnetSprint(id: $id) {
12
13
  id
13
14
  name
14
15
  projectId
16
+ project {
17
+ id
18
+ name
19
+ }
15
20
  status
16
21
  startDate
17
22
  endDate
@@ -21,6 +26,7 @@ var h = o`
21
26
  workItems {
22
27
  id
23
28
  key
29
+ title
24
30
  status
25
31
  storyPoints
26
32
  assigneeId
@@ -28,7 +34,7 @@ var h = o`
28
34
  }
29
35
  }
30
36
  }
31
- `, g = o`
37
+ `, v = c`
32
38
  query GetSprintCapacity($sprintId: ID!) {
33
39
  planmagnetSprintCapacity(sprintId: $sprintId) {
34
40
  sprintId
@@ -48,262 +54,275 @@ var h = o`
48
54
  }
49
55
  }
50
56
  `;
51
- function _() {
52
- let o = t("sprintId"), { data: _, loading: v, error: y } = s(h, {
53
- variables: { id: o },
54
- skip: !o
55
- }), { data: b } = s(g, {
56
- variables: { sprintId: o },
57
- skip: !o
57
+ function y() {
58
+ let c = t("sprintId"), { data: y, loading: b, error: x } = l(_, {
59
+ variables: { id: c },
60
+ skip: !c
61
+ }), { data: S } = l(v, {
62
+ variables: { sprintId: c },
63
+ skip: !c
58
64
  });
59
- if (!o) return /* @__PURE__ */ c("div", {
65
+ if (!c) return /* @__PURE__ */ u("div", {
60
66
  className: "p-6 text-muted-foreground",
61
67
  children: "No planning session selected. Open one from the list."
62
68
  });
63
- if (v) return /* @__PURE__ */ c("div", {
69
+ if (b) return /* @__PURE__ */ u("div", {
64
70
  className: "flex items-center justify-center p-12",
65
- children: /* @__PURE__ */ c("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
71
+ children: /* @__PURE__ */ u("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
66
72
  });
67
- if (y) return /* @__PURE__ */ l("div", {
73
+ if (x) return /* @__PURE__ */ d("div", {
68
74
  className: "p-6 text-red-500",
69
- children: ["Error loading planning session: ", y.message]
75
+ children: ["Error loading planning session: ", x.message]
70
76
  });
71
- let x = _?.planmagnetSprint;
72
- if (!x) return /* @__PURE__ */ c("div", {
77
+ let C = y?.planmagnetSprint;
78
+ if (!C) return /* @__PURE__ */ u("div", {
73
79
  className: "p-6 text-muted-foreground",
74
80
  children: "Planning session not found."
75
81
  });
76
- let S = b?.planmagnetSprintCapacity, C = {};
77
- for (let e of S?.members ?? []) C[e.actorId] = {
82
+ let w = S?.planmagnetSprintCapacity, T = {};
83
+ for (let e of w?.members ?? []) T[e.actorId] = {
78
84
  employeeName: e.employeeName,
79
85
  allocatedHours: e.allocatedHours,
80
86
  billableHours: e.billableHours,
81
87
  utilizationPercent: e.utilizationPercent
82
88
  };
83
- let w = x.workItems ?? [], T = w.reduce((e, t) => e + (t.storyPoints ?? 0), 0);
84
- w.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0);
85
- let E = x.capacity ?? T, D = E > 0 ? Math.round(T / E * 100) : 0, O = E - T, k = [...new Set(w.map((e) => e.assigneeId).filter(Boolean))];
86
- return /* @__PURE__ */ l("div", {
89
+ let E = C.workItems ?? [], D = E.reduce((e, t) => e + (t.storyPoints ?? 0), 0);
90
+ E.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0);
91
+ let O = C.capacity ?? D, k = O > 0 ? Math.round(D / O * 100) : 0, A = O - D, j = [...new Set(E.map((e) => e.assigneeId).filter(Boolean))];
92
+ return /* @__PURE__ */ d("div", {
87
93
  className: "space-y-6 p-6",
88
- children: [/* @__PURE__ */ l("div", {
94
+ children: [/* @__PURE__ */ d("div", {
89
95
  className: "flex items-center justify-between",
90
- children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("h1", {
96
+ children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h1", {
91
97
  className: "text-2xl font-semibold",
92
- children: x.name
93
- }), /* @__PURE__ */ l("p", {
98
+ children: C.name
99
+ }), /* @__PURE__ */ d("p", {
94
100
  className: "text-sm text-muted-foreground",
95
101
  children: [
96
- x.projectId,
102
+ C.project?.name ?? "Unknown project",
97
103
  " ·",
98
104
  " ",
99
- x.startDate ? new Date(x.startDate).toLocaleDateString() : "—",
105
+ C.startDate ? new Date(C.startDate).toLocaleDateString() : "—",
100
106
  " -",
101
107
  " ",
102
- x.endDate ? new Date(x.endDate).toLocaleDateString() : "—"
108
+ C.endDate ? new Date(C.endDate).toLocaleDateString() : "—"
103
109
  ]
104
- })] }), /* @__PURE__ */ c("div", {
105
- className: "flex gap-2",
106
- children: /* @__PURE__ */ c(u, {
110
+ })] }), /* @__PURE__ */ d("div", {
111
+ className: "flex items-start gap-3",
112
+ children: [/* @__PURE__ */ u(f, {
107
113
  className: "capitalize",
108
- children: x.status.toLowerCase().replace("_", " ")
109
- })
114
+ children: C.status.toLowerCase().replace("_", " ")
115
+ }), /* @__PURE__ */ u(n, {
116
+ sprintId: C.id,
117
+ sprintName: C.name,
118
+ status: C.status
119
+ })]
110
120
  })]
111
- }), /* @__PURE__ */ l("div", {
121
+ }), /* @__PURE__ */ d("div", {
112
122
  className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
113
- children: [/* @__PURE__ */ l("div", {
123
+ children: [/* @__PURE__ */ d("div", {
114
124
  className: "lg:col-span-2 space-y-6",
115
- children: [/* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(m, { children: "Capacity Allocation" }) }), /* @__PURE__ */ l(f, {
116
- className: "space-y-4",
117
- children: [/* @__PURE__ */ l("div", {
118
- className: "grid grid-cols-3 gap-4 text-center",
119
- children: [
120
- /* @__PURE__ */ l("div", {
121
- className: "p-4 bg-muted rounded-md",
122
- children: [/* @__PURE__ */ c("p", {
123
- className: "text-2xl font-bold",
124
- children: E
125
- }), /* @__PURE__ */ c("p", {
126
- className: "text-xs text-muted-foreground",
127
- children: "Capacity"
128
- })]
129
- }),
130
- /* @__PURE__ */ l("div", {
131
- className: "p-4 bg-primary/10 rounded-md",
132
- children: [/* @__PURE__ */ c("p", {
133
- className: "text-2xl font-bold text-primary",
134
- children: T
135
- }), /* @__PURE__ */ c("p", {
136
- className: "text-xs text-muted-foreground",
137
- children: "Allocated"
138
- })]
139
- }),
140
- /* @__PURE__ */ l("div", {
141
- className: "p-4 bg-muted rounded-md",
142
- children: [/* @__PURE__ */ c("p", {
143
- className: "text-2xl font-bold",
144
- children: O > 0 ? O : 0
145
- }), /* @__PURE__ */ c("p", {
146
- className: "text-xs text-muted-foreground",
147
- children: "Remaining"
148
- })]
149
- })
150
- ]
151
- }), /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ l("div", {
152
- className: "flex justify-between text-sm mb-2",
153
- children: [/* @__PURE__ */ c("span", {
154
- className: "text-muted-foreground",
155
- children: "Utilization"
156
- }), /* @__PURE__ */ l("span", {
157
- className: "font-medium",
158
- children: [D, "%"]
159
- })]
160
- }), /* @__PURE__ */ c("div", {
161
- className: "w-full h-4 rounded-full bg-muted overflow-hidden",
162
- children: /* @__PURE__ */ c("div", {
163
- className: `h-full rounded-full ${D > 90 ? "bg-red-400" : "bg-green-400"}`,
164
- style: { width: `${Math.min(D, 100)}%` }
165
- })
166
- })] })]
167
- })] }), /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ l(m, { children: [
168
- "Team Members (",
169
- k.length,
170
- ")"
171
- ] }) }), /* @__PURE__ */ l(f, { children: [
172
- /* @__PURE__ */ l("div", {
173
- className: "grid grid-cols-2 gap-3",
174
- children: [k.map((t) => {
175
- let n = C[t];
176
- return /* @__PURE__ */ l("div", {
177
- className: "flex items-center gap-2 border rounded-md p-2.5",
178
- children: [/* @__PURE__ */ c("div", {
179
- className: "h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-medium text-primary",
180
- children: t.slice(0, 2).toUpperCase()
181
- }), /* @__PURE__ */ l("div", {
182
- className: "min-w-0",
183
- children: [/* @__PURE__ */ c("span", {
184
- className: "text-sm truncate block",
185
- children: n?.employeeName ?? /* @__PURE__ */ c(e, { id: t })
186
- }), n && /* @__PURE__ */ l("span", {
125
+ children: [
126
+ /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ u(g, { children: "Capacity Allocation" }) }), /* @__PURE__ */ d(m, {
127
+ className: "space-y-4",
128
+ children: [/* @__PURE__ */ d("div", {
129
+ className: "grid grid-cols-3 gap-4 text-center",
130
+ children: [
131
+ /* @__PURE__ */ d("div", {
132
+ className: "p-4 bg-muted rounded-md",
133
+ children: [/* @__PURE__ */ u("p", {
134
+ className: "text-2xl font-bold",
135
+ children: O
136
+ }), /* @__PURE__ */ u("p", {
187
137
  className: "text-xs text-muted-foreground",
188
- children: [
189
- n.allocatedHours,
190
- "h allocated (",
191
- n.billableHours,
192
- "h billable)",
193
- n.utilizationPercent != null && ` · ${n.utilizationPercent}% util`
194
- ]
138
+ children: "Capacity"
195
139
  })]
196
- })]
197
- }, t);
198
- }), k.length === 0 && /* @__PURE__ */ c("p", {
199
- className: "text-sm text-muted-foreground col-span-2 text-center py-4",
200
- children: "No team members assigned."
201
- })]
202
- }),
203
- S?.crewfoundryUnavailable && k.length > 0 && /* @__PURE__ */ c("p", {
204
- className: "text-xs text-muted-foreground mt-3 border-t pt-3",
205
- children: "Workforce allocation from CrewFoundry is unavailable for this workspace — no CrewFoundry module provisioned yet."
206
- }),
207
- !S?.crewfoundryUnavailable && (S?.unmatchedAssigneeIds?.length ?? 0) > 0 && /* @__PURE__ */ l("p", {
208
- className: "text-xs text-muted-foreground mt-3 border-t pt-3",
209
- children: [S.unmatchedAssigneeIds.length, " assignee(s) have no matching CrewFoundry employee record — allocation hours shown only for matched employees."]
210
- })
211
- ] })] })]
212
- }), /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(m, { children: "Session Info" }) }), /* @__PURE__ */ l(f, {
140
+ }),
141
+ /* @__PURE__ */ d("div", {
142
+ className: "p-4 bg-primary/10 rounded-md",
143
+ children: [/* @__PURE__ */ u("p", {
144
+ className: "text-2xl font-bold text-primary",
145
+ children: D
146
+ }), /* @__PURE__ */ u("p", {
147
+ className: "text-xs text-muted-foreground",
148
+ children: "Allocated"
149
+ })]
150
+ }),
151
+ /* @__PURE__ */ d("div", {
152
+ className: "p-4 bg-muted rounded-md",
153
+ children: [/* @__PURE__ */ u("p", {
154
+ className: "text-2xl font-bold",
155
+ children: A > 0 ? A : 0
156
+ }), /* @__PURE__ */ u("p", {
157
+ className: "text-xs text-muted-foreground",
158
+ children: "Remaining"
159
+ })]
160
+ })
161
+ ]
162
+ }), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("div", {
163
+ className: "flex justify-between text-sm mb-2",
164
+ children: [/* @__PURE__ */ u("span", {
165
+ className: "text-muted-foreground",
166
+ children: "Utilization"
167
+ }), /* @__PURE__ */ d("span", {
168
+ className: "font-medium",
169
+ children: [k, "%"]
170
+ })]
171
+ }), /* @__PURE__ */ u("div", {
172
+ className: "w-full h-4 rounded-full bg-muted overflow-hidden",
173
+ children: /* @__PURE__ */ u("div", {
174
+ className: `h-full rounded-full ${k > 90 ? "bg-red-400" : "bg-green-400"}`,
175
+ style: { width: `${Math.min(k, 100)}%` }
176
+ })
177
+ })] })]
178
+ })] }),
179
+ /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ u(g, { children: "Work Items" }) }), /* @__PURE__ */ u(m, { children: /* @__PURE__ */ u(r, {
180
+ sprintId: C.id,
181
+ projectId: C.projectId,
182
+ status: C.status,
183
+ items: E
184
+ }) })] }),
185
+ /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, { children: [
186
+ "Team Members (",
187
+ j.length,
188
+ ")"
189
+ ] }) }), /* @__PURE__ */ d(m, { children: [
190
+ /* @__PURE__ */ d("div", {
191
+ className: "grid grid-cols-2 gap-3",
192
+ children: [j.map((t) => {
193
+ let n = T[t];
194
+ return /* @__PURE__ */ d("div", {
195
+ className: "flex items-center gap-2 border rounded-md p-2.5",
196
+ children: [/* @__PURE__ */ u("div", {
197
+ className: "h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-medium text-primary",
198
+ children: t.slice(0, 2).toUpperCase()
199
+ }), /* @__PURE__ */ d("div", {
200
+ className: "min-w-0",
201
+ children: [/* @__PURE__ */ u("span", {
202
+ className: "text-sm truncate block",
203
+ children: n?.employeeName ?? /* @__PURE__ */ u(e, { id: t })
204
+ }), n && /* @__PURE__ */ d("span", {
205
+ className: "text-xs text-muted-foreground",
206
+ children: [
207
+ n.allocatedHours,
208
+ "h allocated (",
209
+ n.billableHours,
210
+ "h billable)",
211
+ n.utilizationPercent != null && ` · ${n.utilizationPercent}% util`
212
+ ]
213
+ })]
214
+ })]
215
+ }, t);
216
+ }), j.length === 0 && /* @__PURE__ */ u("p", {
217
+ className: "text-sm text-muted-foreground col-span-2 text-center py-4",
218
+ children: "No team members assigned."
219
+ })]
220
+ }),
221
+ w?.crewfoundryUnavailable && j.length > 0 && /* @__PURE__ */ u("p", {
222
+ className: "text-xs text-muted-foreground mt-3 border-t pt-3",
223
+ children: "Workforce allocation from CrewFoundry is unavailable for this workspace — no CrewFoundry module provisioned yet."
224
+ }),
225
+ !w?.crewfoundryUnavailable && (w?.unmatchedAssigneeIds?.length ?? 0) > 0 && /* @__PURE__ */ d("p", {
226
+ className: "text-xs text-muted-foreground mt-3 border-t pt-3",
227
+ children: [w.unmatchedAssigneeIds.length, " assignee(s) have no matching CrewFoundry employee record — allocation hours shown only for matched employees."]
228
+ })
229
+ ] })] })
230
+ ]
231
+ }), /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ u(g, { children: "Session Info" }) }), /* @__PURE__ */ d(m, {
213
232
  className: "space-y-3 text-sm",
214
233
  children: [
215
- /* @__PURE__ */ l("div", {
234
+ /* @__PURE__ */ d("div", {
216
235
  className: "flex items-center gap-2",
217
236
  children: [
218
- /* @__PURE__ */ c(i, { className: "h-4 w-4 text-muted-foreground" }),
219
- /* @__PURE__ */ c("span", {
237
+ /* @__PURE__ */ u(o, { className: "h-4 w-4 text-muted-foreground" }),
238
+ /* @__PURE__ */ u("span", {
220
239
  className: "text-muted-foreground",
221
240
  children: "Project:"
222
241
  }),
223
- /* @__PURE__ */ c("span", {
242
+ /* @__PURE__ */ u("span", {
224
243
  className: "font-medium",
225
- children: x.projectId
244
+ children: C.project?.name ?? "Unknown project"
226
245
  })
227
246
  ]
228
247
  }),
229
- /* @__PURE__ */ l("div", {
248
+ /* @__PURE__ */ d("div", {
230
249
  className: "flex items-center gap-2",
231
250
  children: [
232
- /* @__PURE__ */ c(r, { className: "h-4 w-4 text-muted-foreground" }),
233
- /* @__PURE__ */ c("span", {
251
+ /* @__PURE__ */ u(a, { className: "h-4 w-4 text-muted-foreground" }),
252
+ /* @__PURE__ */ u("span", {
234
253
  className: "text-muted-foreground",
235
254
  children: "Start:"
236
255
  }),
237
- /* @__PURE__ */ c("span", {
256
+ /* @__PURE__ */ u("span", {
238
257
  className: "font-medium",
239
- children: x.startDate ? new Date(x.startDate).toLocaleDateString() : "Not set"
258
+ children: C.startDate ? new Date(C.startDate).toLocaleDateString() : "Not set"
240
259
  })
241
260
  ]
242
261
  }),
243
- /* @__PURE__ */ l("div", {
262
+ /* @__PURE__ */ d("div", {
244
263
  className: "flex items-center gap-2",
245
264
  children: [
246
- /* @__PURE__ */ c(r, { className: "h-4 w-4 text-muted-foreground" }),
247
- /* @__PURE__ */ c("span", {
265
+ /* @__PURE__ */ u(a, { className: "h-4 w-4 text-muted-foreground" }),
266
+ /* @__PURE__ */ u("span", {
248
267
  className: "text-muted-foreground",
249
268
  children: "End:"
250
269
  }),
251
- /* @__PURE__ */ c("span", {
270
+ /* @__PURE__ */ u("span", {
252
271
  className: "font-medium",
253
- children: x.endDate ? new Date(x.endDate).toLocaleDateString() : "Not set"
272
+ children: C.endDate ? new Date(C.endDate).toLocaleDateString() : "Not set"
254
273
  })
255
274
  ]
256
275
  }),
257
- /* @__PURE__ */ l("div", {
276
+ /* @__PURE__ */ d("div", {
258
277
  className: "flex items-center gap-2",
259
278
  children: [
260
- /* @__PURE__ */ c(a, { className: "h-4 w-4 text-muted-foreground" }),
261
- /* @__PURE__ */ c("span", {
279
+ /* @__PURE__ */ u(s, { className: "h-4 w-4 text-muted-foreground" }),
280
+ /* @__PURE__ */ u("span", {
262
281
  className: "text-muted-foreground",
263
282
  children: "Assignees:"
264
283
  }),
265
- /* @__PURE__ */ c("span", {
284
+ /* @__PURE__ */ u("span", {
266
285
  className: "font-medium",
267
- children: k.length
286
+ children: j.length
268
287
  })
269
288
  ]
270
289
  }),
271
- /* @__PURE__ */ l("div", {
290
+ /* @__PURE__ */ d("div", {
272
291
  className: "flex items-center gap-2",
273
292
  children: [
274
- /* @__PURE__ */ c(n, { className: "h-4 w-4 text-muted-foreground" }),
275
- /* @__PURE__ */ c("span", {
293
+ /* @__PURE__ */ u(i, { className: "h-4 w-4 text-muted-foreground" }),
294
+ /* @__PURE__ */ u("span", {
276
295
  className: "text-muted-foreground",
277
296
  children: "Capacity:"
278
297
  }),
279
- /* @__PURE__ */ l("span", {
298
+ /* @__PURE__ */ d("span", {
280
299
  className: "font-medium",
281
- children: [E, " pts"]
300
+ children: [O, " pts"]
282
301
  })
283
302
  ]
284
303
  }),
285
- x.velocity != null && /* @__PURE__ */ l("div", {
304
+ C.velocity != null && /* @__PURE__ */ d("div", {
286
305
  className: "flex items-center gap-2",
287
306
  children: [
288
- /* @__PURE__ */ c(n, { className: "h-4 w-4 text-muted-foreground" }),
289
- /* @__PURE__ */ c("span", {
307
+ /* @__PURE__ */ u(i, { className: "h-4 w-4 text-muted-foreground" }),
308
+ /* @__PURE__ */ u("span", {
290
309
  className: "text-muted-foreground",
291
310
  children: "Velocity:"
292
311
  }),
293
- /* @__PURE__ */ l("span", {
312
+ /* @__PURE__ */ d("span", {
294
313
  className: "font-medium",
295
- children: [x.velocity, " pts"]
314
+ children: [C.velocity, " pts"]
296
315
  })
297
316
  ]
298
317
  }),
299
- x.goal && /* @__PURE__ */ l("div", {
318
+ C.goal && /* @__PURE__ */ d("div", {
300
319
  className: "border-t pt-3 mt-3",
301
- children: [/* @__PURE__ */ c("p", {
320
+ children: [/* @__PURE__ */ u("p", {
302
321
  className: "text-muted-foreground text-xs mb-1",
303
322
  children: "Goal"
304
- }), /* @__PURE__ */ c("p", {
323
+ }), /* @__PURE__ */ u("p", {
305
324
  className: "text-sm",
306
- children: x.goal
325
+ children: C.goal
307
326
  })]
308
327
  })
309
328
  ]
@@ -312,6 +331,6 @@ function _() {
312
331
  });
313
332
  }
314
333
  //#endregion
315
- export { _ as ProjectsPlanningDetailPagePage };
334
+ export { y as ProjectsPlanningDetailPagePage };
316
335
 
317
336
  //# sourceMappingURL=ProjectsPlanningDetailPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsPlanningDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, Users, BarChart3, FolderKanban } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { PersonName } from '../people/People';\n\nconst GET_SPRINT_DETAIL = gql`\n query GetSprintDetail($id: ID!) {\n planmagnetSprint(id: $id) {\n id\n name\n projectId\n status\n startDate\n endDate\n goal\n capacity\n velocity\n workItems {\n id\n key\n status\n storyPoints\n assigneeId\n priority\n }\n }\n }\n`;\n\n// Real cross-layer CrewFoundry workforce capacity (BOFF-4444). Fails open on\n// the backend — an unreachable/unprovisioned CrewFoundry never blocks this\n// page, it just yields crewfoundryUnavailable: true, handled below.\nconst GET_SPRINT_CAPACITY = gql`\n query GetSprintCapacity($sprintId: ID!) {\n planmagnetSprintCapacity(sprintId: $sprintId) {\n sprintId\n crewfoundryUnavailable\n unmatchedAssigneeIds\n members {\n actorId\n employeeName\n department\n position\n allocatedHours\n billableHours\n utilizationPercent\n assignedStoryPoints\n assignedWorkItemCount\n }\n }\n }\n`;\n\nexport function ProjectsPlanningDetailPagePage() {\n const sprintId = useEntityId('sprintId');\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_SPRINT_DETAIL, {\n variables: { id: sprintId },\n skip: !sprintId,\n });\n\n // Real cross-layer CrewFoundry capacity (BOFF-4444) — called unconditionally\n // (with `skip`) to keep hook order stable across the loading/error/loaded\n // renders above.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { data: capacityData } = useQuery<Record<string, any>>(GET_SPRINT_CAPACITY, {\n variables: { sprintId },\n skip: !sprintId,\n });\n\n if (!sprintId)\n return (\n <div className=\"p-6 text-muted-foreground\">\n No planning session selected. Open one from the list.\n </div>\n );\n if (loading)\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-red-500\">Error loading planning session: {error.message}</div>;\n\n const sprint = data?.planmagnetSprint;\n if (!sprint) return <div className=\"p-6 text-muted-foreground\">Planning session not found.</div>;\n\n const capacity = capacityData?.planmagnetSprintCapacity;\n const allocationByAssignee: Record<\n string,\n {\n employeeName: string;\n allocatedHours: number;\n billableHours: number;\n utilizationPercent: number | null;\n }\n > = {};\n for (const member of capacity?.members ?? []) {\n allocationByAssignee[member.actorId] = {\n employeeName: member.employeeName,\n allocatedHours: member.allocatedHours,\n billableHours: member.billableHours,\n utilizationPercent: member.utilizationPercent,\n };\n }\n\n const workItems = sprint.workItems ?? [];\n const totalPoints = workItems.reduce(\n (sum: number, wi: { storyPoints: number | null }) => sum + (wi.storyPoints ?? 0),\n 0\n );\n const completedPoints = workItems\n .filter((wi: { status: string }) => wi.status === 'DONE')\n .reduce((sum: number, wi: { storyPoints: number | null }) => sum + (wi.storyPoints ?? 0), 0);\n const allocatedCapacity = sprint.capacity ?? totalPoints;\n const capPct = allocatedCapacity > 0 ? Math.round((totalPoints / allocatedCapacity) * 100) : 0;\n const remaining = allocatedCapacity - totalPoints;\n\n // Derive unique assignees from work items\n const uniqueAssignees = [\n ...new Set(\n workItems\n .map((wi: { assigneeId: string | null }) => wi.assigneeId)\n .filter(Boolean) as string[]\n ),\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{sprint.name}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {sprint.projectId} &middot;{' '}\n {sprint.startDate ? new Date(sprint.startDate).toLocaleDateString() : '—'} -{' '}\n {sprint.endDate ? new Date(sprint.endDate).toLocaleDateString() : '—'}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Badge className=\"capitalize\">{sprint.status.toLowerCase().replace('_', ' ')}</Badge>\n </div>\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Capacity Allocation</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-3 gap-4 text-center\">\n <div className=\"p-4 bg-muted rounded-md\">\n <p className=\"text-2xl font-bold\">{allocatedCapacity}</p>\n <p className=\"text-xs text-muted-foreground\">Capacity</p>\n </div>\n <div className=\"p-4 bg-primary/10 rounded-md\">\n <p className=\"text-2xl font-bold text-primary\">{totalPoints}</p>\n <p className=\"text-xs text-muted-foreground\">Allocated</p>\n </div>\n <div className=\"p-4 bg-muted rounded-md\">\n <p className=\"text-2xl font-bold\">{remaining > 0 ? remaining : 0}</p>\n <p className=\"text-xs text-muted-foreground\">Remaining</p>\n </div>\n </div>\n <div>\n <div className=\"flex justify-between text-sm mb-2\">\n <span className=\"text-muted-foreground\">Utilization</span>\n <span className=\"font-medium\">{capPct}%</span>\n </div>\n <div className=\"w-full h-4 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${capPct > 90 ? 'bg-red-400' : 'bg-green-400'}`}\n style={{ width: `${Math.min(capPct, 100)}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Team Members ({uniqueAssignees.length})</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-3\">\n {uniqueAssignees.map((assigneeId: string) => {\n const workforce = allocationByAssignee[assigneeId];\n return (\n <div\n key={assigneeId}\n className=\"flex items-center gap-2 border rounded-md p-2.5\"\n >\n <div className=\"h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-medium text-primary\">\n {assigneeId.slice(0, 2).toUpperCase()}\n </div>\n <div className=\"min-w-0\">\n <span className=\"text-sm truncate block\">\n {workforce?.employeeName ?? <PersonName id={assigneeId} />}\n </span>\n {workforce && (\n <span className=\"text-xs text-muted-foreground\">\n {workforce.allocatedHours}h allocated ({workforce.billableHours}h\n billable)\n {workforce.utilizationPercent != null &&\n ` · ${workforce.utilizationPercent}% util`}\n </span>\n )}\n </div>\n </div>\n );\n })}\n {uniqueAssignees.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-2 text-center py-4\">\n No team members assigned.\n </p>\n )}\n </div>\n {/* Real cross-layer CrewFoundry workforce allocation (BOFF-4444). */}\n {capacity?.crewfoundryUnavailable && uniqueAssignees.length > 0 && (\n <p className=\"text-xs text-muted-foreground mt-3 border-t pt-3\">\n Workforce allocation from CrewFoundry is unavailable for this workspace — no\n CrewFoundry module provisioned yet.\n </p>\n )}\n {!capacity?.crewfoundryUnavailable &&\n (capacity?.unmatchedAssigneeIds?.length ?? 0) > 0 && (\n <p className=\"text-xs text-muted-foreground mt-3 border-t pt-3\">\n {capacity.unmatchedAssigneeIds.length} assignee(s) have no matching CrewFoundry\n employee record — allocation hours shown only for matched employees.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n <Card>\n <CardHeader>\n <CardTitle>Session Info</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <FolderKanban className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Project:</span>\n <span className=\"font-medium\">{sprint.projectId}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Start:</span>\n <span className=\"font-medium\">\n {sprint.startDate ? new Date(sprint.startDate).toLocaleDateString() : 'Not set'}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">End:</span>\n <span className=\"font-medium\">\n {sprint.endDate ? new Date(sprint.endDate).toLocaleDateString() : 'Not set'}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <Users className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Assignees:</span>\n <span className=\"font-medium\">{uniqueAssignees.length}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Capacity:</span>\n <span className=\"font-medium\">{allocatedCapacity} pts</span>\n </div>\n {sprint.velocity != null && (\n <div className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Velocity:</span>\n <span className=\"font-medium\">{sprint.velocity} pts</span>\n </div>\n )}\n {sprint.goal && (\n <div className=\"border-t pt-3 mt-3\">\n <p className=\"text-muted-foreground text-xs mb-1\">Goal</p>\n <p className=\"text-sm\">{sprint.goal}</p>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectsPlanningDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, Users, BarChart3, FolderKanban } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { PersonName } from '../people/People';\nimport { SprintLifecycleActions, SprintWorkItemsPanel } from '../sprints/SprintControls';\n\nconst GET_SPRINT_DETAIL = gql`\n query GetSprintDetail($id: ID!) {\n planmagnetSprint(id: $id) {\n id\n name\n projectId\n project {\n id\n name\n }\n status\n startDate\n endDate\n goal\n capacity\n velocity\n workItems {\n id\n key\n title\n status\n storyPoints\n assigneeId\n priority\n }\n }\n }\n`;\n\n// Real cross-layer CrewFoundry workforce capacity (BOFF-4444). Fails open on\n// the backend — an unreachable/unprovisioned CrewFoundry never blocks this\n// page, it just yields crewfoundryUnavailable: true, handled below.\nconst GET_SPRINT_CAPACITY = gql`\n query GetSprintCapacity($sprintId: ID!) {\n planmagnetSprintCapacity(sprintId: $sprintId) {\n sprintId\n crewfoundryUnavailable\n unmatchedAssigneeIds\n members {\n actorId\n employeeName\n department\n position\n allocatedHours\n billableHours\n utilizationPercent\n assignedStoryPoints\n assignedWorkItemCount\n }\n }\n }\n`;\n\nexport function ProjectsPlanningDetailPagePage() {\n const sprintId = useEntityId('sprintId');\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_SPRINT_DETAIL, {\n variables: { id: sprintId },\n skip: !sprintId,\n });\n\n // Real cross-layer CrewFoundry capacity (BOFF-4444) — called unconditionally\n // (with `skip`) to keep hook order stable across the loading/error/loaded\n // renders above.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { data: capacityData } = useQuery<Record<string, any>>(GET_SPRINT_CAPACITY, {\n variables: { sprintId },\n skip: !sprintId,\n });\n\n if (!sprintId)\n return (\n <div className=\"p-6 text-muted-foreground\">\n No planning session selected. Open one from the list.\n </div>\n );\n if (loading)\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-red-500\">Error loading planning session: {error.message}</div>;\n\n const sprint = data?.planmagnetSprint;\n if (!sprint) return <div className=\"p-6 text-muted-foreground\">Planning session not found.</div>;\n\n const capacity = capacityData?.planmagnetSprintCapacity;\n const allocationByAssignee: Record<\n string,\n {\n employeeName: string;\n allocatedHours: number;\n billableHours: number;\n utilizationPercent: number | null;\n }\n > = {};\n for (const member of capacity?.members ?? []) {\n allocationByAssignee[member.actorId] = {\n employeeName: member.employeeName,\n allocatedHours: member.allocatedHours,\n billableHours: member.billableHours,\n utilizationPercent: member.utilizationPercent,\n };\n }\n\n const workItems = sprint.workItems ?? [];\n const totalPoints = workItems.reduce(\n (sum: number, wi: { storyPoints: number | null }) => sum + (wi.storyPoints ?? 0),\n 0\n );\n const completedPoints = workItems\n .filter((wi: { status: string }) => wi.status === 'DONE')\n .reduce((sum: number, wi: { storyPoints: number | null }) => sum + (wi.storyPoints ?? 0), 0);\n const allocatedCapacity = sprint.capacity ?? totalPoints;\n const capPct = allocatedCapacity > 0 ? Math.round((totalPoints / allocatedCapacity) * 100) : 0;\n const remaining = allocatedCapacity - totalPoints;\n\n // Derive unique assignees from work items\n const uniqueAssignees = [\n ...new Set(\n workItems\n .map((wi: { assigneeId: string | null }) => wi.assigneeId)\n .filter(Boolean) as string[]\n ),\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{sprint.name}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {sprint.project?.name ?? 'Unknown project'} &middot;{' '}\n {sprint.startDate ? new Date(sprint.startDate).toLocaleDateString() : '—'} -{' '}\n {sprint.endDate ? new Date(sprint.endDate).toLocaleDateString() : '—'}\n </p>\n </div>\n <div className=\"flex items-start gap-3\">\n <Badge className=\"capitalize\">{sprint.status.toLowerCase().replace('_', ' ')}</Badge>\n <SprintLifecycleActions\n sprintId={sprint.id}\n sprintName={sprint.name}\n status={sprint.status}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Capacity Allocation</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-3 gap-4 text-center\">\n <div className=\"p-4 bg-muted rounded-md\">\n <p className=\"text-2xl font-bold\">{allocatedCapacity}</p>\n <p className=\"text-xs text-muted-foreground\">Capacity</p>\n </div>\n <div className=\"p-4 bg-primary/10 rounded-md\">\n <p className=\"text-2xl font-bold text-primary\">{totalPoints}</p>\n <p className=\"text-xs text-muted-foreground\">Allocated</p>\n </div>\n <div className=\"p-4 bg-muted rounded-md\">\n <p className=\"text-2xl font-bold\">{remaining > 0 ? remaining : 0}</p>\n <p className=\"text-xs text-muted-foreground\">Remaining</p>\n </div>\n </div>\n <div>\n <div className=\"flex justify-between text-sm mb-2\">\n <span className=\"text-muted-foreground\">Utilization</span>\n <span className=\"font-medium\">{capPct}%</span>\n </div>\n <div className=\"w-full h-4 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${capPct > 90 ? 'bg-red-400' : 'bg-green-400'}`}\n style={{ width: `${Math.min(capPct, 100)}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Work Items</CardTitle>\n </CardHeader>\n <CardContent>\n <SprintWorkItemsPanel\n sprintId={sprint.id}\n projectId={sprint.projectId}\n status={sprint.status}\n items={workItems}\n />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Team Members ({uniqueAssignees.length})</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-3\">\n {uniqueAssignees.map((assigneeId: string) => {\n const workforce = allocationByAssignee[assigneeId];\n return (\n <div\n key={assigneeId}\n className=\"flex items-center gap-2 border rounded-md p-2.5\"\n >\n <div className=\"h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-medium text-primary\">\n {assigneeId.slice(0, 2).toUpperCase()}\n </div>\n <div className=\"min-w-0\">\n <span className=\"text-sm truncate block\">\n {workforce?.employeeName ?? <PersonName id={assigneeId} />}\n </span>\n {workforce && (\n <span className=\"text-xs text-muted-foreground\">\n {workforce.allocatedHours}h allocated ({workforce.billableHours}h\n billable)\n {workforce.utilizationPercent != null &&\n ` · ${workforce.utilizationPercent}% util`}\n </span>\n )}\n </div>\n </div>\n );\n })}\n {uniqueAssignees.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-2 text-center py-4\">\n No team members assigned.\n </p>\n )}\n </div>\n {/* Real cross-layer CrewFoundry workforce allocation (BOFF-4444). */}\n {capacity?.crewfoundryUnavailable && uniqueAssignees.length > 0 && (\n <p className=\"text-xs text-muted-foreground mt-3 border-t pt-3\">\n Workforce allocation from CrewFoundry is unavailable for this workspace — no\n CrewFoundry module provisioned yet.\n </p>\n )}\n {!capacity?.crewfoundryUnavailable &&\n (capacity?.unmatchedAssigneeIds?.length ?? 0) > 0 && (\n <p className=\"text-xs text-muted-foreground mt-3 border-t pt-3\">\n {capacity.unmatchedAssigneeIds.length} assignee(s) have no matching CrewFoundry\n employee record — allocation hours shown only for matched employees.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n <Card>\n <CardHeader>\n <CardTitle>Session Info</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <FolderKanban className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Project:</span>\n <span className=\"font-medium\">{sprint.project?.name ?? 'Unknown project'}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Start:</span>\n <span className=\"font-medium\">\n {sprint.startDate ? new Date(sprint.startDate).toLocaleDateString() : 'Not set'}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">End:</span>\n <span className=\"font-medium\">\n {sprint.endDate ? new Date(sprint.endDate).toLocaleDateString() : 'Not set'}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <Users className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Assignees:</span>\n <span className=\"font-medium\">{uniqueAssignees.length}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Capacity:</span>\n <span className=\"font-medium\">{allocatedCapacity} pts</span>\n </div>\n {sprint.velocity != null && (\n <div className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Velocity:</span>\n <span className=\"font-medium\">{sprint.velocity} pts</span>\n </div>\n )}\n {sprint.goal && (\n <div className=\"border-t pt-3 mt-3\">\n <p className=\"text-muted-foreground text-xs mb-1\">Goal</p>\n <p className=\"text-sm\">{sprint.goal}</p>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAUA,IAAM,IAAoB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCvB,IAAsB,CAAG;;;;;;;;;;;;;;;;;;;;AAqB/B,SAAgB,IAAiC;CAC/C,IAAM,IAAW,EAAY,WAAW,EAElC,EACJ,SACA,YACA,aAGA,EAA8B,GAAmB;EAC/C,WAAW,EAAE,IAAI,GAAU;EAC3B,MAAM,CAAC;EACR,CAAC,EAME,EAAE,MAAM,MAAiB,EAA8B,GAAqB;EAChF,WAAW,EAAE,aAAU;EACvB,MAAM,CAAC;EACR,CAAC;AAEF,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAErC,CAAA;AAEV,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAEV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAkC,oCAAiC,EAAM,QAAc;;CAEhG,IAAM,IAAS,GAAM;AACrB,KAAI,CAAC,EAAQ,QAAO,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAAiC,CAAA;CAEhG,IAAM,IAAW,GAAc,0BACzB,IAQF,EAAE;AACN,MAAK,IAAM,KAAU,GAAU,WAAW,EAAE,CAC1C,GAAqB,EAAO,WAAW;EACrC,cAAc,EAAO;EACrB,gBAAgB,EAAO;EACvB,eAAe,EAAO;EACtB,oBAAoB,EAAO;EAC5B;CAGH,IAAM,IAAY,EAAO,aAAa,EAAE,EAClC,IAAc,EAAU,QAC3B,GAAa,MAAuC,KAAO,EAAG,eAAe,IAC9E,EACD;AACuB,GACrB,QAAQ,MAA2B,EAAG,WAAW,OAAO,CACxD,QAAQ,GAAa,MAAuC,KAAO,EAAG,eAAe,IAAI,EAAE;CAC9F,IAAM,IAAoB,EAAO,YAAY,GACvC,IAAS,IAAoB,IAAI,KAAK,MAAO,IAAc,IAAqB,IAAI,GAAG,GACvF,IAAY,IAAoB,GAGhC,IAAkB,CACtB,GAAG,IAAI,IACL,EACG,KAAK,MAAsC,EAAG,WAAW,CACzD,OAAO,QAAQ,CACnB,CACF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAA0B,EAAO;IAAU,CAAA,EACzD,kBAAC,KAAD;IAAG,WAAU;cAAb;KACG,EAAO,SAAS,QAAQ;KAAkB;KAAU;KACpD,EAAO,YAAY,IAAI,KAAK,EAAO,UAAU,CAAC,oBAAoB,GAAG;KAAI;KAAG;KAC5E,EAAO,UAAU,IAAI,KAAK,EAAO,QAAQ,CAAC,oBAAoB,GAAG;KAChE;MACA,EAAA,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAO,WAAU;eAAc,EAAO,OAAO,aAAa,CAAC,QAAQ,KAAK,IAAI;KAAS,CAAA,EACrF,kBAAC,GAAD;KACE,UAAU,EAAO;KACjB,YAAY,EAAO;KACnB,QAAQ,EAAO;KACf,CAAA,CACE;MACF;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,uBAA+B,CAAA,EAC/B,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAsB;UAAsB,CAAA,EACzD,kBAAC,KAAD;UAAG,WAAU;oBAAgC;UAAY,CAAA,CACrD;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAmC;UAAgB,CAAA,EAChE,kBAAC,KAAD;UAAG,WAAU;oBAAgC;UAAa,CAAA,CACtD;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAsB,IAAY,IAAI,IAAY;UAAM,CAAA,EACrE,kBAAC,KAAD;UAAG,WAAU;oBAAgC;UAAa,CAAA,CACtD;;QACF;UACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAkB,CAAA,EAC1D,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA+B,GAAO,IAAQ;UAC1C;UACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QACE,WAAW,uBAAuB,IAAS,KAAK,eAAe;QAC/D,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,GAAQ,IAAI,CAAC,IAAI;QAC7C,CAAA;OACE,CAAA,CACF,EAAA,CAAA,CACM;QACT,EAAA,CAAA;KACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,cAAsB,CAAA,EACtB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MACE,UAAU,EAAO;MACjB,WAAW,EAAO;MAClB,QAAQ,EAAO;MACf,OAAO;MACP,CAAA,EACU,CAAA,CACT,EAAA,CAAA;KACP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;MAAW;MAAe,EAAgB;MAAO;MAAa,EAAA,CAAA,EACnD,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,EAAgB,KAAK,MAAuB;QAC3C,IAAM,IAAY,EAAqB;AACvC,eACE,kBAAC,OAAD;SAEE,WAAU;mBAFZ,CAIE,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAW,MAAM,GAAG,EAAE,CAAC,aAAa;UACjC,CAAA,EACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,GAAW,gBAAgB,kBAAC,GAAD,EAAY,IAAI,GAAc,CAAA;WACrD,CAAA,EACN,KACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACG,EAAU;YAAe;YAAc,EAAU;YAAc;YAE/D,EAAU,sBAAsB,QAC/B,MAAM,EAAU,mBAAmB;YAChC;aAEL;YACF;WAnBC,EAmBD;SAER,EACD,EAAgB,WAAW,KAC1B,kBAAC,KAAD;QAAG,WAAU;kBAA4D;QAErE,CAAA,CAEF;;MAEL,GAAU,0BAA0B,EAAgB,SAAS,KAC5D,kBAAC,KAAD;OAAG,WAAU;iBAAmD;OAG5D,CAAA;MAEL,CAAC,GAAU,2BACT,GAAU,sBAAsB,UAAU,KAAK,KAC9C,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAS,qBAAqB,QAAO,iHAEpC;;MAEI,EAAA,CAAA,CACT,EAAA,CAAA;KACH;OACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,gBAAwB,CAAA,EACxB,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAc,WAAU,iCAAkC,CAAA;OAC1D,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAe,CAAA;OACvD,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAO,SAAS,QAAQ;QAAyB,CAAA;OAC5E;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;OAC3D,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAa,CAAA;OACrD,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAO,YAAY,IAAI,KAAK,EAAO,UAAU,CAAC,oBAAoB,GAAG;QACjE,CAAA;OACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;OAC3D,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAW,CAAA;OACnD,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAO,UAAU,IAAI,KAAK,EAAO,QAAQ,CAAC,oBAAoB,GAAG;QAC7D,CAAA;OACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA;OACnD,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAiB,CAAA;OACzD,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAgB;QAAc,CAAA;OACzD;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAW,WAAU,iCAAkC,CAAA;OACvD,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAgB,CAAA;OACxD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA+B,GAAkB,OAAW;;OACxD;;KACL,EAAO,YAAY,QAClB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAW,WAAU,iCAAkC,CAAA;OACvD,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAgB,CAAA;OACxD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA+B,EAAO,UAAS,OAAW;;OACtD;;KAEP,EAAO,QACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAqC;OAAQ,CAAA,EAC1D,kBAAC,KAAD;OAAG,WAAU;iBAAW,EAAO;OAAS,CAAA,CACpC;;KAEI;MACT,EAAA,CAAA,CACH;KACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsPlanningEditPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningEditPagePage.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,4BAA4B,4CAuL3C"}
1
+ {"version":3,"file":"ProjectsPlanningEditPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningEditPagePage.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,4BAA4B,4CAqL3C"}