@burdenoff/microfe-planmagnet 2026.830.1 → 2026.923.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +52 -0
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/hooks/useEntityId.d.ts +14 -0
- package/dist/planmagnet/hooks/useEntityId.d.ts.map +1 -0
- package/dist/planmagnet/hooks/useEntityId.js +15 -0
- package/dist/planmagnet/hooks/useEntityId.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +148 -144
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -84
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js +95 -91
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +117 -113
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +74 -70
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +21 -17
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +21 -16
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +67 -63
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +61 -56
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +107 -99
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +94 -90
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +46 -41
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +110 -106
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +2 -2
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +2 -2
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +67 -66
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.d.ts +29 -0
- package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -0
- package/dist/planmagnet/utils/rowNavigation.js +20 -0
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEntityId as e } from "../hooks/useEntityId.js";
|
|
2
2
|
import { BarChart3 as t, CalendarClock as n, FolderKanban as r, Users as i } from "lucide-react";
|
|
3
3
|
import { gql as a } from "@apollo/client";
|
|
4
4
|
import { useQuery as o } from "@apollo/client/react";
|
|
@@ -6,26 +6,24 @@ import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
|
6
6
|
import { Badge as l, Card as u, CardContent as d, CardHeader as f, CardTitle as p } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx
|
|
8
8
|
var m = a`
|
|
9
|
-
query GetSprintDetail($
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
query GetSprintDetail($id: ID!) {
|
|
10
|
+
planmagnetSprint(id: $id) {
|
|
11
|
+
id
|
|
12
|
+
name
|
|
13
|
+
projectId
|
|
14
|
+
status
|
|
15
|
+
startDate
|
|
16
|
+
endDate
|
|
17
|
+
goal
|
|
18
|
+
capacity
|
|
19
|
+
velocity
|
|
20
|
+
workItems {
|
|
12
21
|
id
|
|
13
|
-
|
|
14
|
-
projectId
|
|
22
|
+
key
|
|
15
23
|
status
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
capacity
|
|
20
|
-
velocity
|
|
21
|
-
workItems {
|
|
22
|
-
id
|
|
23
|
-
key
|
|
24
|
-
status
|
|
25
|
-
storyPoints
|
|
26
|
-
assigneeId
|
|
27
|
-
priority
|
|
28
|
-
}
|
|
24
|
+
storyPoints
|
|
25
|
+
assigneeId
|
|
26
|
+
priority
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
}
|
|
@@ -50,9 +48,16 @@ var m = a`
|
|
|
50
48
|
}
|
|
51
49
|
`;
|
|
52
50
|
function g() {
|
|
53
|
-
let
|
|
54
|
-
variables: {
|
|
55
|
-
skip: !
|
|
51
|
+
let a = e("sprintId"), { data: g, loading: _, error: v } = o(m, {
|
|
52
|
+
variables: { id: a },
|
|
53
|
+
skip: !a
|
|
54
|
+
}), { data: y } = o(h, {
|
|
55
|
+
variables: { sprintId: a },
|
|
56
|
+
skip: !a
|
|
57
|
+
});
|
|
58
|
+
if (!a) return /* @__PURE__ */ s("div", {
|
|
59
|
+
className: "p-6 text-muted-foreground",
|
|
60
|
+
children: "No planning session selected. Open one from the list."
|
|
56
61
|
});
|
|
57
62
|
if (_) return /* @__PURE__ */ s("div", {
|
|
58
63
|
className: "flex items-center justify-center p-12",
|
|
@@ -62,44 +67,44 @@ function g() {
|
|
|
62
67
|
className: "p-6 text-red-500",
|
|
63
68
|
children: ["Error loading planning session: ", v.message]
|
|
64
69
|
});
|
|
65
|
-
let
|
|
66
|
-
if (!
|
|
70
|
+
let b = g?.planmagnetSprint;
|
|
71
|
+
if (!b) return /* @__PURE__ */ s("div", {
|
|
67
72
|
className: "p-6 text-muted-foreground",
|
|
68
73
|
children: "Planning session not found."
|
|
69
74
|
});
|
|
70
|
-
let
|
|
71
|
-
for (let e of
|
|
75
|
+
let x = y?.planmagnetSprintCapacity, S = {};
|
|
76
|
+
for (let e of x?.members ?? []) S[e.actorId] = {
|
|
72
77
|
employeeName: e.employeeName,
|
|
73
78
|
allocatedHours: e.allocatedHours,
|
|
74
79
|
billableHours: e.billableHours,
|
|
75
80
|
utilizationPercent: e.utilizationPercent
|
|
76
81
|
};
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
let
|
|
82
|
+
let C = b.workItems ?? [], w = C.reduce((e, t) => e + (t.storyPoints ?? 0), 0);
|
|
83
|
+
C.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0);
|
|
84
|
+
let T = b.capacity ?? w, E = T > 0 ? Math.round(w / T * 100) : 0, D = T - w, O = [...new Set(C.map((e) => e.assigneeId).filter(Boolean))];
|
|
80
85
|
return /* @__PURE__ */ c("div", {
|
|
81
86
|
className: "space-y-6 p-6",
|
|
82
87
|
children: [/* @__PURE__ */ c("div", {
|
|
83
88
|
className: "flex items-center justify-between",
|
|
84
89
|
children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("h1", {
|
|
85
90
|
className: "text-2xl font-semibold",
|
|
86
|
-
children:
|
|
91
|
+
children: b.name
|
|
87
92
|
}), /* @__PURE__ */ c("p", {
|
|
88
93
|
className: "text-sm text-muted-foreground",
|
|
89
94
|
children: [
|
|
90
|
-
|
|
95
|
+
b.projectId,
|
|
91
96
|
" ·",
|
|
92
97
|
" ",
|
|
93
|
-
|
|
98
|
+
b.startDate ? new Date(b.startDate).toLocaleDateString() : "—",
|
|
94
99
|
" -",
|
|
95
100
|
" ",
|
|
96
|
-
|
|
101
|
+
b.endDate ? new Date(b.endDate).toLocaleDateString() : "—"
|
|
97
102
|
]
|
|
98
103
|
})] }), /* @__PURE__ */ s("div", {
|
|
99
104
|
className: "flex gap-2",
|
|
100
105
|
children: /* @__PURE__ */ s(l, {
|
|
101
106
|
className: "capitalize",
|
|
102
|
-
children:
|
|
107
|
+
children: b.status.toLowerCase().replace("_", " ")
|
|
103
108
|
})
|
|
104
109
|
})]
|
|
105
110
|
}), /* @__PURE__ */ c("div", {
|
|
@@ -115,7 +120,7 @@ function g() {
|
|
|
115
120
|
className: "p-4 bg-muted rounded-md",
|
|
116
121
|
children: [/* @__PURE__ */ s("p", {
|
|
117
122
|
className: "text-2xl font-bold",
|
|
118
|
-
children:
|
|
123
|
+
children: T
|
|
119
124
|
}), /* @__PURE__ */ s("p", {
|
|
120
125
|
className: "text-xs text-muted-foreground",
|
|
121
126
|
children: "Capacity"
|
|
@@ -125,7 +130,7 @@ function g() {
|
|
|
125
130
|
className: "p-4 bg-primary/10 rounded-md",
|
|
126
131
|
children: [/* @__PURE__ */ s("p", {
|
|
127
132
|
className: "text-2xl font-bold text-primary",
|
|
128
|
-
children:
|
|
133
|
+
children: w
|
|
129
134
|
}), /* @__PURE__ */ s("p", {
|
|
130
135
|
className: "text-xs text-muted-foreground",
|
|
131
136
|
children: "Allocated"
|
|
@@ -135,7 +140,7 @@ function g() {
|
|
|
135
140
|
className: "p-4 bg-muted rounded-md",
|
|
136
141
|
children: [/* @__PURE__ */ s("p", {
|
|
137
142
|
className: "text-2xl font-bold",
|
|
138
|
-
children:
|
|
143
|
+
children: D > 0 ? D : 0
|
|
139
144
|
}), /* @__PURE__ */ s("p", {
|
|
140
145
|
className: "text-xs text-muted-foreground",
|
|
141
146
|
children: "Remaining"
|
|
@@ -149,24 +154,24 @@ function g() {
|
|
|
149
154
|
children: "Utilization"
|
|
150
155
|
}), /* @__PURE__ */ c("span", {
|
|
151
156
|
className: "font-medium",
|
|
152
|
-
children: [
|
|
157
|
+
children: [E, "%"]
|
|
153
158
|
})]
|
|
154
159
|
}), /* @__PURE__ */ s("div", {
|
|
155
160
|
className: "w-full h-4 rounded-full bg-muted overflow-hidden",
|
|
156
161
|
children: /* @__PURE__ */ s("div", {
|
|
157
|
-
className: `h-full rounded-full ${
|
|
158
|
-
style: { width: `${Math.min(
|
|
162
|
+
className: `h-full rounded-full ${E > 90 ? "bg-red-400" : "bg-green-400"}`,
|
|
163
|
+
style: { width: `${Math.min(E, 100)}%` }
|
|
159
164
|
})
|
|
160
165
|
})] })]
|
|
161
166
|
})] }), /* @__PURE__ */ c(u, { children: [/* @__PURE__ */ s(f, { children: /* @__PURE__ */ c(p, { children: [
|
|
162
167
|
"Team Members (",
|
|
163
|
-
|
|
168
|
+
O.length,
|
|
164
169
|
")"
|
|
165
170
|
] }) }), /* @__PURE__ */ c(d, { children: [
|
|
166
171
|
/* @__PURE__ */ c("div", {
|
|
167
172
|
className: "grid grid-cols-2 gap-3",
|
|
168
|
-
children: [
|
|
169
|
-
let t =
|
|
173
|
+
children: [O.map((e) => {
|
|
174
|
+
let t = S[e];
|
|
170
175
|
return /* @__PURE__ */ c("div", {
|
|
171
176
|
className: "flex items-center gap-2 border rounded-md p-2.5",
|
|
172
177
|
children: [/* @__PURE__ */ s("div", {
|
|
@@ -189,18 +194,18 @@ function g() {
|
|
|
189
194
|
})]
|
|
190
195
|
})]
|
|
191
196
|
}, e);
|
|
192
|
-
}),
|
|
197
|
+
}), O.length === 0 && /* @__PURE__ */ s("p", {
|
|
193
198
|
className: "text-sm text-muted-foreground col-span-2 text-center py-4",
|
|
194
199
|
children: "No team members assigned."
|
|
195
200
|
})]
|
|
196
201
|
}),
|
|
197
|
-
|
|
202
|
+
x?.crewfoundryUnavailable && O.length > 0 && /* @__PURE__ */ s("p", {
|
|
198
203
|
className: "text-xs text-muted-foreground mt-3 border-t pt-3",
|
|
199
204
|
children: "Workforce allocation from CrewFoundry is unavailable for this workspace — no CrewFoundry module provisioned yet."
|
|
200
205
|
}),
|
|
201
|
-
!
|
|
206
|
+
!x?.crewfoundryUnavailable && (x?.unmatchedAssigneeIds?.length ?? 0) > 0 && /* @__PURE__ */ c("p", {
|
|
202
207
|
className: "text-xs text-muted-foreground mt-3 border-t pt-3",
|
|
203
|
-
children: [
|
|
208
|
+
children: [x.unmatchedAssigneeIds.length, " assignee(s) have no matching CrewFoundry employee record — allocation hours shown only for matched employees."]
|
|
204
209
|
})
|
|
205
210
|
] })] })]
|
|
206
211
|
}), /* @__PURE__ */ c(u, { children: [/* @__PURE__ */ s(f, { children: /* @__PURE__ */ s(p, { children: "Session Info" }) }), /* @__PURE__ */ c(d, {
|
|
@@ -216,7 +221,7 @@ function g() {
|
|
|
216
221
|
}),
|
|
217
222
|
/* @__PURE__ */ s("span", {
|
|
218
223
|
className: "font-medium",
|
|
219
|
-
children:
|
|
224
|
+
children: b.projectId
|
|
220
225
|
})
|
|
221
226
|
]
|
|
222
227
|
}),
|
|
@@ -230,7 +235,7 @@ function g() {
|
|
|
230
235
|
}),
|
|
231
236
|
/* @__PURE__ */ s("span", {
|
|
232
237
|
className: "font-medium",
|
|
233
|
-
children:
|
|
238
|
+
children: b.startDate ? new Date(b.startDate).toLocaleDateString() : "Not set"
|
|
234
239
|
})
|
|
235
240
|
]
|
|
236
241
|
}),
|
|
@@ -244,7 +249,7 @@ function g() {
|
|
|
244
249
|
}),
|
|
245
250
|
/* @__PURE__ */ s("span", {
|
|
246
251
|
className: "font-medium",
|
|
247
|
-
children:
|
|
252
|
+
children: b.endDate ? new Date(b.endDate).toLocaleDateString() : "Not set"
|
|
248
253
|
})
|
|
249
254
|
]
|
|
250
255
|
}),
|
|
@@ -258,7 +263,7 @@ function g() {
|
|
|
258
263
|
}),
|
|
259
264
|
/* @__PURE__ */ s("span", {
|
|
260
265
|
className: "font-medium",
|
|
261
|
-
children:
|
|
266
|
+
children: O.length
|
|
262
267
|
})
|
|
263
268
|
]
|
|
264
269
|
}),
|
|
@@ -272,11 +277,11 @@ function g() {
|
|
|
272
277
|
}),
|
|
273
278
|
/* @__PURE__ */ c("span", {
|
|
274
279
|
className: "font-medium",
|
|
275
|
-
children: [
|
|
280
|
+
children: [T, " pts"]
|
|
276
281
|
})
|
|
277
282
|
]
|
|
278
283
|
}),
|
|
279
|
-
|
|
284
|
+
b.velocity != null && /* @__PURE__ */ c("div", {
|
|
280
285
|
className: "flex items-center gap-2",
|
|
281
286
|
children: [
|
|
282
287
|
/* @__PURE__ */ s(t, { className: "h-4 w-4 text-muted-foreground" }),
|
|
@@ -286,18 +291,18 @@ function g() {
|
|
|
286
291
|
}),
|
|
287
292
|
/* @__PURE__ */ c("span", {
|
|
288
293
|
className: "font-medium",
|
|
289
|
-
children: [
|
|
294
|
+
children: [b.velocity, " pts"]
|
|
290
295
|
})
|
|
291
296
|
]
|
|
292
297
|
}),
|
|
293
|
-
|
|
298
|
+
b.goal && /* @__PURE__ */ c("div", {
|
|
294
299
|
className: "border-t pt-3 mt-3",
|
|
295
300
|
children: [/* @__PURE__ */ s("p", {
|
|
296
301
|
className: "text-muted-foreground text-xs mb-1",
|
|
297
302
|
children: "Goal"
|
|
298
303
|
}), /* @__PURE__ */ s("p", {
|
|
299
304
|
className: "text-sm",
|
|
300
|
-
children:
|
|
305
|
+
children: b.goal
|
|
301
306
|
})]
|
|
302
307
|
})
|
|
303
308
|
]
|
|
@@ -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 { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\nconst GET_SPRINT_DETAIL = gql`\n query GetSprintDetail($workspaceId: ID!) {\n planmagnetSprints(workspaceId: $workspaceId, pagination: { first: 1 }) {\n items {\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\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 { workspaceId } = usePlanMagnet();\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: { workspaceId: workspaceId ?? 'default' },\n });\n\n const sprintId = data?.planmagnetSprints?.items?.[0]?.id;\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 (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?.planmagnetSprints?.items?.[0];\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} ·{' '}\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 ?? 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';\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} ·{' '}\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 ?? 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsPlanningEditPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningEditPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsPlanningEditPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningEditPagePage.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,4BAA4B,4CAuL3C"}
|