@burdenoff/microfe-planmagnet 2026.928.6 → 2026.928.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.d.ts +5 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +15 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.js +13 -12
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +7 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +45 -8
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +74 -59
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +153 -63
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +138 -112
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +77 -70
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { useEntityId as n } from "../hooks/useEntityId.js";
|
|
3
3
|
import { ProjectSelect as r } from "../controls/Pickers.js";
|
|
4
|
-
import {
|
|
5
|
-
import { AlertTriangle as
|
|
6
|
-
import { gql as
|
|
7
|
-
import { useQuery as
|
|
8
|
-
import { jsx as
|
|
9
|
-
import { Badge as
|
|
4
|
+
import { useEffect as i, useState as a } from "react";
|
|
5
|
+
import { AlertTriangle as o, Route as s } from "lucide-react";
|
|
6
|
+
import { gql as c } from "@apollo/client";
|
|
7
|
+
import { useQuery as l } from "@apollo/client/react";
|
|
8
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
9
|
+
import { Badge as f, Card as p, CardContent as m, CardHeader as h, CardTitle as g } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx
|
|
11
|
-
var
|
|
11
|
+
var _ = c`
|
|
12
12
|
query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {
|
|
13
13
|
planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {
|
|
14
14
|
totalDurationMinutes
|
|
@@ -24,141 +24,231 @@ var g = s`
|
|
|
24
24
|
length
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
`,
|
|
27
|
+
`, v = c`
|
|
28
28
|
query GetCriticalPathProject($id: ID!) {
|
|
29
29
|
planmagnetProject(id: $id) {
|
|
30
30
|
id
|
|
31
31
|
name
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
`, y = 100, b = c`
|
|
35
|
+
query GetNonCriticalWorkItems(
|
|
36
|
+
$workspaceId: ID!
|
|
37
|
+
$filter: PlanmagnetWorkItemFilter
|
|
38
|
+
$pagination: PlanmagnetPaginationInput
|
|
39
|
+
) {
|
|
40
|
+
planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {
|
|
41
|
+
items {
|
|
42
|
+
id
|
|
43
|
+
title
|
|
44
|
+
key
|
|
45
|
+
}
|
|
46
|
+
pageInfo {
|
|
47
|
+
hasNextPage
|
|
48
|
+
endCursor
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
`;
|
|
35
|
-
function
|
|
53
|
+
function x(e) {
|
|
36
54
|
if (e <= 0) return "0 days";
|
|
37
55
|
let t = Math.round(e);
|
|
38
56
|
if (t < 60) return `${Math.max(1, t)}m`;
|
|
39
57
|
let n = Math.round(t / 60), r = Math.floor(n / 24), i = n % 24;
|
|
40
58
|
return r === 0 ? `${i}h` : i > 0 ? `${r}d ${i}h` : `${r} day${r === 1 ? "" : "s"}`;
|
|
41
59
|
}
|
|
42
|
-
function
|
|
43
|
-
let
|
|
60
|
+
function S() {
|
|
61
|
+
let c = n(), { workspaceId: S } = e(), C = t(), [w, T] = a(""), { data: E, loading: D, error: O } = l(_, {
|
|
44
62
|
variables: {
|
|
45
|
-
workspaceId:
|
|
46
|
-
projectId:
|
|
63
|
+
workspaceId: S ?? "",
|
|
64
|
+
projectId: c
|
|
47
65
|
},
|
|
48
|
-
skip: !
|
|
49
|
-
}), { data:
|
|
50
|
-
variables: { id:
|
|
51
|
-
skip: !
|
|
52
|
-
})
|
|
53
|
-
|
|
66
|
+
skip: !S || !c
|
|
67
|
+
}), { data: k } = l(v, {
|
|
68
|
+
variables: { id: c },
|
|
69
|
+
skip: !c
|
|
70
|
+
}), A = E?.planmagnetCriticalPath.path ?? [], j = A.length > 0, M = A.map((e) => e.id), { data: N, loading: P, error: F, fetchMore: I } = l(b, {
|
|
71
|
+
variables: {
|
|
72
|
+
workspaceId: S ?? "",
|
|
73
|
+
filter: {
|
|
74
|
+
projectId: c,
|
|
75
|
+
excludeIds: M
|
|
76
|
+
},
|
|
77
|
+
pagination: { first: y }
|
|
78
|
+
},
|
|
79
|
+
skip: !S || !c || !j
|
|
80
|
+
}), L = N?.planmagnetWorkItems.pageInfo, [R, z] = a(null);
|
|
81
|
+
if (i(() => {
|
|
82
|
+
if (R || !L?.hasNextPage || !L.endCursor) return;
|
|
83
|
+
let e = L.endCursor;
|
|
84
|
+
I({
|
|
85
|
+
variables: { pagination: {
|
|
86
|
+
first: y,
|
|
87
|
+
after: e
|
|
88
|
+
} },
|
|
89
|
+
updateQuery: (e, { fetchMoreResult: t }) => ({ planmagnetWorkItems: {
|
|
90
|
+
...t.planmagnetWorkItems,
|
|
91
|
+
items: [...e.planmagnetWorkItems.items, ...t.planmagnetWorkItems.items]
|
|
92
|
+
} })
|
|
93
|
+
}).then((e) => {
|
|
94
|
+
let t = e?.error;
|
|
95
|
+
t && z(t.message ?? "Unknown error");
|
|
96
|
+
}).catch((e) => z(e instanceof Error ? e.message : String(e)));
|
|
97
|
+
}, [
|
|
98
|
+
R,
|
|
99
|
+
L?.hasNextPage,
|
|
100
|
+
L?.endCursor,
|
|
101
|
+
I
|
|
102
|
+
]), !c) return /* @__PURE__ */ d("div", {
|
|
54
103
|
className: "space-y-6 p-6",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("h1", {
|
|
56
105
|
className: "text-2xl font-semibold flex items-center gap-2",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
58
|
-
}), /* @__PURE__ */
|
|
106
|
+
children: [/* @__PURE__ */ u(s, { className: "h-5 w-5 text-primary" }), "Critical Path"]
|
|
107
|
+
}), /* @__PURE__ */ u("p", {
|
|
59
108
|
className: "text-sm text-muted-foreground",
|
|
60
109
|
children: "Choose a project to see its critical path."
|
|
61
|
-
})] }), /* @__PURE__ */
|
|
110
|
+
})] }), /* @__PURE__ */ u(p, { children: /* @__PURE__ */ d(m, {
|
|
62
111
|
className: "pt-6 space-y-3",
|
|
63
112
|
children: [
|
|
64
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ u("label", {
|
|
65
114
|
htmlFor: "critical-path-project",
|
|
66
115
|
className: "text-sm font-medium",
|
|
67
116
|
children: "Project"
|
|
68
117
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
118
|
+
/* @__PURE__ */ u(r, {
|
|
70
119
|
id: "critical-path-project",
|
|
71
|
-
value:
|
|
72
|
-
onChange:
|
|
120
|
+
value: w,
|
|
121
|
+
onChange: T,
|
|
73
122
|
noneLabel: "Choose a project…"
|
|
74
123
|
}),
|
|
75
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ u("button", {
|
|
76
125
|
type: "button",
|
|
77
126
|
className: "text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50",
|
|
78
|
-
disabled: !
|
|
79
|
-
onClick: () =>
|
|
127
|
+
disabled: !w,
|
|
128
|
+
onClick: () => C(`/projects/critical-path-page/${encodeURIComponent(w)}`),
|
|
80
129
|
children: "View critical path →"
|
|
81
130
|
})
|
|
82
131
|
]
|
|
83
132
|
}) })]
|
|
84
133
|
});
|
|
85
|
-
if (
|
|
134
|
+
if (D && !E) return /* @__PURE__ */ u("div", {
|
|
86
135
|
className: "flex items-center justify-center p-12",
|
|
87
|
-
children: /* @__PURE__ */
|
|
136
|
+
children: /* @__PURE__ */ u("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
88
137
|
});
|
|
89
|
-
if (
|
|
138
|
+
if (O) return /* @__PURE__ */ d("div", {
|
|
90
139
|
className: "p-6 text-red-500",
|
|
91
|
-
children: ["Error computing critical path: ",
|
|
140
|
+
children: ["Error computing critical path: ", O.message]
|
|
92
141
|
});
|
|
93
|
-
let
|
|
94
|
-
return /* @__PURE__ */
|
|
142
|
+
let B = E?.planmagnetCriticalPath.slack ?? {}, V = E?.planmagnetCriticalPath.totalDurationMinutes ?? 0, H = E?.planmagnetDependencyCycles ?? [], U = k?.planmagnetProject?.name, W = [...N?.planmagnetWorkItems.items ?? []].sort((e, t) => (B[e.id] ?? 0) - (B[t.id] ?? 0));
|
|
143
|
+
return /* @__PURE__ */ d("div", {
|
|
95
144
|
className: "space-y-6 p-6",
|
|
96
145
|
children: [
|
|
97
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ u("div", {
|
|
98
147
|
className: "flex items-center justify-between",
|
|
99
|
-
children: /* @__PURE__ */
|
|
148
|
+
children: /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("h1", {
|
|
100
149
|
className: "text-2xl font-semibold flex items-center gap-2",
|
|
101
|
-
children: [/* @__PURE__ */
|
|
102
|
-
}), /* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ u(s, { className: "h-5 w-5 text-primary" }), "Critical Path"]
|
|
151
|
+
}), /* @__PURE__ */ d("p", {
|
|
103
152
|
className: "text-sm text-muted-foreground",
|
|
104
|
-
children: [
|
|
153
|
+
children: [U ?? "This project", " — the longest chain of dependent work; a delay on any node here delays the whole chain."]
|
|
105
154
|
})] })
|
|
106
155
|
}),
|
|
107
|
-
|
|
156
|
+
H.length > 0 && /* @__PURE__ */ d("div", {
|
|
108
157
|
role: "alert",
|
|
109
158
|
className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
159
|
+
children: [/* @__PURE__ */ u(o, { className: "h-4 w-4 mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("p", {
|
|
111
160
|
className: "font-medium",
|
|
112
161
|
children: [
|
|
113
|
-
|
|
162
|
+
H.length,
|
|
114
163
|
" dependency cycle",
|
|
115
|
-
|
|
164
|
+
H.length === 1 ? "" : "s",
|
|
116
165
|
" detected."
|
|
117
166
|
]
|
|
118
|
-
}), /* @__PURE__ */
|
|
167
|
+
}), /* @__PURE__ */ u("p", { children: "A cycle has no valid schedule, so the path below excludes the items involved. Break each cycle by removing or re-pointing one of its dependencies from the task detail page." })] })]
|
|
119
168
|
}),
|
|
120
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ u(g, { children: "Total duration" }) }), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u("p", {
|
|
121
170
|
className: "text-2xl font-bold",
|
|
122
|
-
children:
|
|
123
|
-
}), /* @__PURE__ */
|
|
171
|
+
children: x(V)
|
|
172
|
+
}), /* @__PURE__ */ u("p", {
|
|
124
173
|
className: "text-xs text-muted-foreground mt-1",
|
|
125
174
|
children: "Work item spans plus dependency lag/lead, summed along the longest chain."
|
|
126
175
|
})] })] }),
|
|
127
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, { children: [
|
|
128
177
|
"Path (",
|
|
129
|
-
|
|
178
|
+
A.length,
|
|
130
179
|
" item",
|
|
131
|
-
|
|
180
|
+
A.length === 1 ? "" : "s",
|
|
132
181
|
")"
|
|
133
|
-
] }) }), /* @__PURE__ */
|
|
182
|
+
] }) }), /* @__PURE__ */ d(m, {
|
|
134
183
|
className: "space-y-2",
|
|
135
|
-
children: [
|
|
184
|
+
children: [A.map((e, t) => /* @__PURE__ */ d("div", {
|
|
136
185
|
className: "flex items-center justify-between gap-3 rounded-md border border-input p-3",
|
|
137
|
-
children: [/* @__PURE__ */
|
|
186
|
+
children: [/* @__PURE__ */ d("div", {
|
|
138
187
|
className: "flex items-center gap-3 min-w-0",
|
|
139
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ u("span", {
|
|
140
189
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground",
|
|
141
190
|
children: t + 1
|
|
142
|
-
}), /* @__PURE__ */
|
|
191
|
+
}), /* @__PURE__ */ d("button", {
|
|
143
192
|
type: "button",
|
|
144
193
|
className: "min-w-0 truncate text-left text-sm font-medium hover:underline",
|
|
145
|
-
onClick: () =>
|
|
194
|
+
onClick: () => C(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
|
|
146
195
|
children: [e.key ? `${e.key} ` : "", e.title ?? "Untitled work item"]
|
|
147
196
|
})]
|
|
148
|
-
}), /* @__PURE__ */
|
|
149
|
-
variant: (
|
|
197
|
+
}), /* @__PURE__ */ u(f, {
|
|
198
|
+
variant: (B[e.id] ?? 0) === 0 ? "default" : "secondary",
|
|
150
199
|
className: "shrink-0",
|
|
151
|
-
children: (
|
|
200
|
+
children: (B[e.id] ?? 0) === 0 ? "On critical path" : `${x(B[e.id] ?? 0)} slack`
|
|
152
201
|
})]
|
|
153
|
-
}, e.id)),
|
|
202
|
+
}, e.id)), A.length === 0 && /* @__PURE__ */ u("p", {
|
|
154
203
|
className: "text-center text-muted-foreground py-8",
|
|
155
204
|
children: "No schedulable dependencies yet. Add dependencies from a task's detail page to compute a critical path."
|
|
156
205
|
})]
|
|
206
|
+
})] }),
|
|
207
|
+
j && /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, { children: [
|
|
208
|
+
"Not on the critical path (",
|
|
209
|
+
W.length,
|
|
210
|
+
" item",
|
|
211
|
+
W.length === 1 ? "" : "s",
|
|
212
|
+
")"
|
|
213
|
+
] }) }), /* @__PURE__ */ d(m, {
|
|
214
|
+
className: "space-y-2",
|
|
215
|
+
children: [
|
|
216
|
+
W.map((e) => /* @__PURE__ */ d("div", {
|
|
217
|
+
className: "flex items-center justify-between gap-3 rounded-md border border-input p-3",
|
|
218
|
+
children: [/* @__PURE__ */ d("button", {
|
|
219
|
+
type: "button",
|
|
220
|
+
className: "min-w-0 truncate text-left text-sm font-medium hover:underline",
|
|
221
|
+
onClick: () => C(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
|
|
222
|
+
children: [e.key ? `${e.key} ` : "", e.title]
|
|
223
|
+
}), /* @__PURE__ */ d(f, {
|
|
224
|
+
variant: "secondary",
|
|
225
|
+
className: "shrink-0",
|
|
226
|
+
children: [x(B[e.id] ?? 0), " slack"]
|
|
227
|
+
})]
|
|
228
|
+
}, e.id)),
|
|
229
|
+
!F && R && /* @__PURE__ */ d("p", {
|
|
230
|
+
className: "text-center text-destructive py-4",
|
|
231
|
+
role: "alert",
|
|
232
|
+
children: ["Some work items could not be loaded, so this list may be incomplete: ", R]
|
|
233
|
+
}),
|
|
234
|
+
F && /* @__PURE__ */ d("p", {
|
|
235
|
+
className: "text-center text-destructive py-8",
|
|
236
|
+
children: ["The rest of this project's work items could not be loaded: ", F.message]
|
|
237
|
+
}),
|
|
238
|
+
!F && P && W.length === 0 && /* @__PURE__ */ u("p", {
|
|
239
|
+
className: "text-center text-muted-foreground py-8",
|
|
240
|
+
children: "Loading…"
|
|
241
|
+
}),
|
|
242
|
+
!F && !P && W.length === 0 && /* @__PURE__ */ u("p", {
|
|
243
|
+
className: "text-center text-muted-foreground py-8",
|
|
244
|
+
children: "Every scheduled work item in this project is on the critical path."
|
|
245
|
+
})
|
|
246
|
+
]
|
|
157
247
|
})] })
|
|
158
248
|
]
|
|
159
249
|
});
|
|
160
250
|
}
|
|
161
251
|
//#endregion
|
|
162
|
-
export {
|
|
252
|
+
export { S as ProjectsCriticalPathPagePage };
|
|
163
253
|
|
|
164
254
|
//# sourceMappingURL=ProjectsCriticalPathPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsCriticalPathPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Route, AlertTriangle } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\n\n/**\n * Critical path view for a project (BOFF-8047). planmagnetCriticalPath / planmagnetDependencyCycles were\n * served and prod-verified (duration-weighted CPM over WorkItemDependency, per the P0 decision that this type\n * is CPM-only) but had no UI. Reached from the sidebar (no project preselects one) and from a project's detail\n * page (\"Critical path\" button).\n */\n\nconst GET_CRITICAL_PATH = gql`\n query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {\n planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {\n totalDurationMinutes\n slack\n path {\n id\n title\n key\n }\n }\n planmagnetDependencyCycles(workspaceId: $workspaceId, projectId: $projectId) {\n workItemIds\n length\n }\n }\n`;\n\nconst GET_CRITICAL_PATH_PROJECT = gql`\n query GetCriticalPathProject($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\nfunction durationLabel(totalMinutes: number): string {\n if (totalMinutes <= 0) return '0 days';\n // Round once, then split. Rounding the day remainder separately printed \"1d 24h\" for 47.5–47.9 h, and fractional\n // minutes (durations are elapsed ms / 60000) printed decimals.\n const roundedMinutes = Math.round(totalMinutes);\n if (roundedMinutes < 60) return `${Math.max(1, roundedMinutes)}m`;\n const totalHours = Math.round(roundedMinutes / 60);\n const days = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n if (days === 0) return `${hours}h`;\n return hours > 0 ? `${days}d ${hours}h` : `${days} day${days === 1 ? '' : 's'}`;\n}\n\nexport function ProjectsCriticalPathPagePage() {\n const projectId = useEntityId();\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [pickerProjectId, setPickerProjectId] = useState('');\n\n const { data, loading, error } = useQuery<{\n planmagnetCriticalPath: {\n totalDurationMinutes: number;\n // The SDL doc comment on this field says \"days\" but the resolver\n // actually returns MINUTES (wspace-planmagnet-svc\n // src/modules/dependency/services/dependency-graph.ts:53, backed by\n // durationMinutes() in dependency.service.ts) — same unit as\n // totalDurationMinutes above. Feed values through durationLabel()\n // below, never render this number bare. SDL doc fix tracked\n // separately; do not \"fix\" this comment to match the SDL.\n slack: Record<string, number>;\n path: { id: string; title: string | null; key: string | null }[];\n };\n planmagnetDependencyCycles: { workItemIds: string[]; length: number }[];\n }>(GET_CRITICAL_PATH, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const { data: projectData } = useQuery<{\n planmagnetProject: { id: string; name: string } | null;\n }>(GET_CRITICAL_PATH_PROJECT, { variables: { id: projectId }, skip: !projectId });\n\n if (!projectId) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Choose a project to see its critical path.\n </p>\n </div>\n <Card>\n <CardContent className=\"pt-6 space-y-3\">\n <label htmlFor=\"critical-path-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <ProjectSelect\n id=\"critical-path-project\"\n value={pickerProjectId}\n onChange={setPickerProjectId}\n noneLabel=\"Choose a project…\"\n />\n <button\n type=\"button\"\n className=\"text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50\"\n disabled={!pickerProjectId}\n onClick={() =>\n navigate(`/projects/critical-path-page/${encodeURIComponent(pickerProjectId)}`)\n }\n >\n View critical path →\n </button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page.\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-red-500\">Error computing critical path: {error.message}</div>;\n\n const path = data?.planmagnetCriticalPath.path ?? [];\n const slack = data?.planmagnetCriticalPath.slack ?? {};\n const totalMinutes = data?.planmagnetCriticalPath.totalDurationMinutes ?? 0;\n const cycles = data?.planmagnetDependencyCycles ?? [];\n const projectName = projectData?.planmagnetProject?.name;\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 flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {projectName ?? 'This project'} — the longest chain of dependent work; a delay on any\n node here delays the whole chain.\n </p>\n </div>\n </div>\n\n {cycles.length > 0 && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive\"\n >\n <AlertTriangle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>\n <p className=\"font-medium\">\n {cycles.length} dependency cycle{cycles.length === 1 ? '' : 's'} detected.\n </p>\n <p>\n A cycle has no valid schedule, so the path below excludes the items involved. Break\n each cycle by removing or re-pointing one of its dependencies from the task detail\n page.\n </p>\n </div>\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Total duration</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-2xl font-bold\">{durationLabel(totalMinutes)}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n Work item spans plus dependency lag/lead, summed along the longest chain.\n </p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n Path ({path.length} item{path.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {path.map((node, index) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground\">\n {index + 1}\n </span>\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title ?? 'Untitled work item'}\n </button>\n </div>\n <Badge\n variant={(slack[node.id] ?? 0) === 0 ? 'default' : 'secondary'}\n className=\"shrink-0\"\n >\n {(slack[node.id] ?? 0) === 0\n ? 'On critical path'\n : `${durationLabel(slack[node.id] ?? 0)} slack`}\n </Badge>\n </div>\n ))}\n {path.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No schedulable dependencies yet. Add dependencies from a task's detail page to\n compute a critical path.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAgBA,IAAM,IAAoB,CAAG;;;;;;;;;;;;;;;;GAkBvB,IAA4B,CAAG;;;;;;;;AASrC,SAAS,EAAc,GAA8B;AACnD,KAAI,KAAgB,EAAG,QAAO;CAG9B,IAAM,IAAiB,KAAK,MAAM,EAAa;AAC/C,KAAI,IAAiB,GAAI,QAAO,GAAG,KAAK,IAAI,GAAG,EAAe,CAAC;CAC/D,IAAM,IAAa,KAAK,MAAM,IAAiB,GAAG,EAC5C,IAAO,KAAK,MAAM,IAAa,GAAG,EAClC,IAAQ,IAAa;AAE3B,QADI,MAAS,IAAU,GAAG,EAAM,KACzB,IAAQ,IAAI,GAAG,EAAK,IAAI,EAAM,KAAK,GAAG,EAAK,MAAM,MAAS,IAAI,KAAK;;AAG5E,SAAgB,IAA+B;CAC7C,IAAM,IAAY,GAAa,EACzB,EAAE,mBAAgB,GAAe,EACjC,IAAW,GAAuB,EAClC,CAAC,GAAiB,KAAsB,EAAS,GAAG,EAEpD,EAAE,SAAM,YAAS,aAAU,EAc9B,GAAmB;EACpB,WAAW;GAAE,aAAa,KAAe;GAAI;GAAW;EACxD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EACI,EAAE,MAAM,MAAgB,EAE3B,GAA2B;EAAE,WAAW,EAAE,IAAI,GAAW;EAAE,MAAM,CAAC;EAAW,CAAC;AAEjF,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAAd,CACE,kBAAC,GAAD,EAAO,WAAU,wBAAyB,CAAA,EAAA,gBAEvC;MACL,kBAAC,KAAD;GAAG,WAAU;aAAgC;GAEzC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACE,kBAAC,SAAD;KAAO,SAAQ;KAAwB,WAAU;eAAsB;KAE/D,CAAA;IACR,kBAAC,GAAD;KACE,IAAG;KACH,OAAO;KACP,UAAU;KACV,WAAU;KACV,CAAA;IACF,kBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,UAAU,CAAC;KACX,eACE,EAAS,gCAAgC,mBAAmB,EAAgB,GAAG;eAElF;KAEQ,CAAA;IACG;MACT,CAAA,CACH;;AAKV,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAEV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAkC,mCAAgC,EAAM,QAAc;;CAE/F,IAAM,IAAO,GAAM,uBAAuB,QAAQ,EAAE,EAC9C,IAAQ,GAAM,uBAAuB,SAAS,EAAE,EAChD,IAAe,GAAM,uBAAuB,wBAAwB,GACpE,IAAS,GAAM,8BAA8B,EAAE,EAC/C,IAAc,GAAa,mBAAmB;AAEpD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACE,kBAAC,GAAD,EAAO,WAAU,wBAAyB,CAAA,EAAA,gBAEvC;QACL,kBAAC,KAAD;KAAG,WAAU;eAAb,CACG,KAAe,gBAAe,2FAE7B;OACA,EAAA,CAAA;IACF,CAAA;GAEL,EAAO,SAAS,KACf,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAe,WAAU,2BAA4B,CAAA,EACrD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAO;MAAO;MAAkB,EAAO,WAAW,IAAI,KAAK;MAAI;MAC9D;QACJ,kBAAC,KAAD,EAAA,UAAG,gLAIC,CAAA,CACA,EAAA,CAAA,CACF;;GAGR,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,kBAA0B,CAAA,EAC1B,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAAsB,EAAc,EAAa;IAAK,CAAA,EACnE,kBAAC,KAAD;IAAG,WAAU;cAAqC;IAE9C,CAAA,CACQ,EAAA,CAAA,CACT,EAAA,CAAA;GAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;IAAW;IACF,EAAK;IAAO;IAAM,EAAK,WAAW,IAAI,KAAK;IAAI;IAC5C,EAAA,CAAA,EACD,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACG,EAAK,KAAK,GAAM,MACf,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,IAAQ;OACJ,CAAA,EACP,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,eACE,EAAS,kCAAkC,mBAAmB,EAAK,GAAG,GAAG;iBAJ7E,CAOG,EAAK,MAAM,GAAG,EAAK,IAAI,KAAK,IAC5B,EAAK,SAAS,qBACR;SACL;SACN,kBAAC,GAAD;MACE,UAAU,EAAM,EAAK,OAAO,OAAO,IAAI,YAAY;MACnD,WAAU;iBAER,EAAM,EAAK,OAAO,OAAO,IACvB,qBACA,GAAG,EAAc,EAAM,EAAK,OAAO,EAAE,CAAC;MACpC,CAAA,CACJ;OA1BC,EAAK,GA0BN,CACN,EACD,EAAK,WAAW,KACf,kBAAC,KAAD;KAAG,WAAU;eAAyC;KAGlD,CAAA,CAEM;MACT,EAAA,CAAA;GACH"}
|
|
1
|
+
{"version":3,"file":"ProjectsCriticalPathPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsCriticalPathPagePage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Route, AlertTriangle } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\n\n/**\n * Critical path view for a project (BOFF-8047). planmagnetCriticalPath / planmagnetDependencyCycles were\n * served and prod-verified (duration-weighted CPM over WorkItemDependency, per the P0 decision that this type\n * is CPM-only) but had no UI. Reached from the sidebar (no project preselects one) and from a project's detail\n * page (\"Critical path\" button).\n */\n\nconst GET_CRITICAL_PATH = gql`\n query GetProjectCriticalPath($workspaceId: ID!, $projectId: ID) {\n planmagnetCriticalPath(workspaceId: $workspaceId, projectId: $projectId) {\n totalDurationMinutes\n slack\n path {\n id\n title\n key\n }\n }\n planmagnetDependencyCycles(workspaceId: $workspaceId, projectId: $projectId) {\n workItemIds\n length\n }\n }\n`;\n\nconst GET_CRITICAL_PATH_PROJECT = gql`\n query GetCriticalPathProject($id: ID!) {\n planmagnetProject(id: $id) {\n id\n name\n }\n }\n`;\n\nconst NON_CRITICAL_PAGE_SIZE = 100;\n\n// Every work item in the project that the critical-path chain does NOT run through (excludeIds is the\n// path's own ids), so the slack map's per-node minutes have somewhere to show up. Paged like\n// ExportWorkItemsButton: a project can have more items than one page, and stopping at the first page\n// would silently drop items with real slack from the list below.\nconst GET_NON_CRITICAL_WORK_ITEMS = gql`\n query GetNonCriticalWorkItems(\n $workspaceId: ID!\n $filter: PlanmagnetWorkItemFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n title\n key\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n`;\n\nfunction durationLabel(totalMinutes: number): string {\n if (totalMinutes <= 0) return '0 days';\n // Round once, then split. Rounding the day remainder separately printed \"1d 24h\" for 47.5–47.9 h, and fractional\n // minutes (durations are elapsed ms / 60000) printed decimals.\n const roundedMinutes = Math.round(totalMinutes);\n if (roundedMinutes < 60) return `${Math.max(1, roundedMinutes)}m`;\n const totalHours = Math.round(roundedMinutes / 60);\n const days = Math.floor(totalHours / 24);\n const hours = totalHours % 24;\n if (days === 0) return `${hours}h`;\n return hours > 0 ? `${days}d ${hours}h` : `${days} day${days === 1 ? '' : 's'}`;\n}\n\nexport function ProjectsCriticalPathPagePage() {\n const projectId = useEntityId();\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [pickerProjectId, setPickerProjectId] = useState('');\n\n const { data, loading, error } = useQuery<{\n planmagnetCriticalPath: {\n totalDurationMinutes: number;\n // The SDL doc comment on this field says \"days\" but the resolver\n // actually returns MINUTES (wspace-planmagnet-svc\n // src/modules/dependency/services/dependency-graph.ts:53, backed by\n // durationMinutes() in dependency.service.ts) — same unit as\n // totalDurationMinutes above. Feed values through durationLabel()\n // below, never render this number bare. SDL doc fix tracked\n // separately; do not \"fix\" this comment to match the SDL.\n slack: Record<string, number>;\n path: { id: string; title: string | null; key: string | null }[];\n };\n planmagnetDependencyCycles: { workItemIds: string[]; length: number }[];\n }>(GET_CRITICAL_PATH, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const { data: projectData } = useQuery<{\n planmagnetProject: { id: string; name: string } | null;\n }>(GET_CRITICAL_PATH_PROJECT, { variables: { id: projectId }, skip: !projectId });\n\n const path = data?.planmagnetCriticalPath.path ?? [];\n const hasCriticalPath = path.length > 0;\n const pathIds = path.map((node) => node.id);\n\n const {\n data: otherData,\n loading: otherLoading,\n error: otherError,\n fetchMore: fetchMoreOther,\n } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; title: string; key: string }[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n }>(GET_NON_CRITICAL_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { projectId, excludeIds: pathIds },\n pagination: { first: NON_CRITICAL_PAGE_SIZE },\n },\n skip: !workspaceId || !projectId || !hasCriticalPath,\n });\n\n const otherPageInfo = otherData?.planmagnetWorkItems.pageInfo;\n // A failed later page must not leave a short list that reads as complete: remember it and say so.\n const [moreError, setMoreError] = useState<string | null>(null);\n useEffect(() => {\n if (moreError || !otherPageInfo?.hasNextPage || !otherPageInfo.endCursor) return;\n const after = otherPageInfo.endCursor;\n fetchMoreOther({\n variables: { pagination: { first: NON_CRITICAL_PAGE_SIZE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetWorkItems: {\n ...fetchMoreResult.planmagnetWorkItems,\n items: [...prev.planmagnetWorkItems.items, ...fetchMoreResult.planmagnetWorkItems.items],\n },\n }),\n })\n .then((result) => {\n // fe-libs queries resolve with { error } instead of rejecting.\n const failure = (result as { error?: { message?: string } } | undefined)?.error;\n if (failure) setMoreError(failure.message ?? 'Unknown error');\n })\n .catch((e: unknown) => setMoreError(e instanceof Error ? e.message : String(e)));\n }, [moreError, otherPageInfo?.hasNextPage, otherPageInfo?.endCursor, fetchMoreOther]);\n\n if (!projectId) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n Choose a project to see its critical path.\n </p>\n </div>\n <Card>\n <CardContent className=\"pt-6 space-y-3\">\n <label htmlFor=\"critical-path-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <ProjectSelect\n id=\"critical-path-project\"\n value={pickerProjectId}\n onChange={setPickerProjectId}\n noneLabel=\"Choose a project…\"\n />\n <button\n type=\"button\"\n className=\"text-sm text-primary hover:underline disabled:pointer-events-none disabled:opacity-50\"\n disabled={!pickerProjectId}\n onClick={() =>\n navigate(`/projects/critical-path-page/${encodeURIComponent(pickerProjectId)}`)\n }\n >\n View critical path →\n </button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n // Only before the first load: a refetch must not blank the page.\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-red-500\">Error computing critical path: {error.message}</div>;\n\n const slack = data?.planmagnetCriticalPath.slack ?? {};\n const totalMinutes = data?.planmagnetCriticalPath.totalDurationMinutes ?? 0;\n const cycles = data?.planmagnetDependencyCycles ?? [];\n const projectName = projectData?.planmagnetProject?.name;\n const otherItems = [...(otherData?.planmagnetWorkItems.items ?? [])].sort(\n (a, b) => (slack[a.id] ?? 0) - (slack[b.id] ?? 0)\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 flex items-center gap-2\">\n <Route className=\"h-5 w-5 text-primary\" />\n Critical Path\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {projectName ?? 'This project'} — the longest chain of dependent work; a delay on any\n node here delays the whole chain.\n </p>\n </div>\n </div>\n\n {cycles.length > 0 && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive\"\n >\n <AlertTriangle className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>\n <p className=\"font-medium\">\n {cycles.length} dependency cycle{cycles.length === 1 ? '' : 's'} detected.\n </p>\n <p>\n A cycle has no valid schedule, so the path below excludes the items involved. Break\n each cycle by removing or re-pointing one of its dependencies from the task detail\n page.\n </p>\n </div>\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Total duration</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-2xl font-bold\">{durationLabel(totalMinutes)}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n Work item spans plus dependency lag/lead, summed along the longest chain.\n </p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n Path ({path.length} item{path.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {path.map((node, index) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-medium text-primary-foreground\">\n {index + 1}\n </span>\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title ?? 'Untitled work item'}\n </button>\n </div>\n <Badge\n variant={(slack[node.id] ?? 0) === 0 ? 'default' : 'secondary'}\n className=\"shrink-0\"\n >\n {(slack[node.id] ?? 0) === 0\n ? 'On critical path'\n : `${durationLabel(slack[node.id] ?? 0)} slack`}\n </Badge>\n </div>\n ))}\n {path.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n No schedulable dependencies yet. Add dependencies from a task's detail page to\n compute a critical path.\n </p>\n )}\n </CardContent>\n </Card>\n\n {hasCriticalPath && (\n <Card>\n <CardHeader>\n <CardTitle>\n Not on the critical path ({otherItems.length} item\n {otherItems.length === 1 ? '' : 's'})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {otherItems.map((node) => (\n <div\n key={node.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(node.id)}`)\n }\n >\n {node.key ? `${node.key} ` : ''}\n {node.title}\n </button>\n <Badge variant=\"secondary\" className=\"shrink-0\">\n {durationLabel(slack[node.id] ?? 0)} slack\n </Badge>\n </div>\n ))}\n {!otherError && moreError && (\n <p className=\"text-center text-destructive py-4\" role=\"alert\">\n Some work items could not be loaded, so this list may be incomplete: {moreError}\n </p>\n )}\n {otherError && (\n <p className=\"text-center text-destructive py-8\">\n The rest of this project's work items could not be loaded: {otherError.message}\n </p>\n )}\n {!otherError && otherLoading && otherItems.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">Loading…</p>\n )}\n {!otherError && !otherLoading && otherItems.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n Every scheduled work item in this project is on the critical path.\n </p>\n )}\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsEditProjectPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsEditProjectPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsEditProjectPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsEditProjectPagePage.tsx"],"names":[],"mappings":"AA8DA,wBAAgB,2BAA2B,4CA+Q1C"}
|