@burdenoff/microfe-planmagnet 2026.1001.1 → 2026.1001.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/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +156 -150
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +19 -15
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/comments/CommentThread.js +36 -36
- package/dist/planmagnet/comments/RoadmapComments.js +21 -21
- package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
- package/dist/planmagnet/controls/FeatureControls.js +60 -60
- package/dist/planmagnet/controls/SyncControls.js +59 -59
- package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
- package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
- package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.js +37 -0
- package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +206 -182
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
- package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
- package/dist/planmagnet/product/ProductControls.js +91 -91
- package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
- package/dist/planmagnet/reports/ShareDialog.js +20 -20
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
- package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
- package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
- package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
- package/package.json +1 -1
|
@@ -3,20 +3,21 @@ import { Tooltip as t } from "../../../node_modules/recharts/es6/component/Toolt
|
|
|
3
3
|
import { ResponsiveContainer as n } from "../../../node_modules/recharts/es6/component/ResponsiveContainer.js";
|
|
4
4
|
import { Bar as r } from "../../../node_modules/recharts/es6/cartesian/Bar.js";
|
|
5
5
|
import { ReferenceLine as i } from "../../../node_modules/recharts/es6/cartesian/ReferenceLine.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
6
|
+
import { ReferenceArea as a } from "../../../node_modules/recharts/es6/cartesian/ReferenceArea.js";
|
|
7
|
+
import { CartesianGrid as o } from "../../../node_modules/recharts/es6/cartesian/CartesianGrid.js";
|
|
8
|
+
import { Line as s } from "../../../node_modules/recharts/es6/cartesian/Line.js";
|
|
9
|
+
import { XAxis as c } from "../../../node_modules/recharts/es6/cartesian/XAxis.js";
|
|
10
|
+
import { YAxis as l } from "../../../node_modules/recharts/es6/cartesian/YAxis.js";
|
|
11
|
+
import { LineChart as u } from "../../../node_modules/recharts/es6/chart/LineChart.js";
|
|
12
|
+
import { BarChart as d } from "../../../node_modules/recharts/es6/chart/BarChart.js";
|
|
13
|
+
import { BarChart3 as f, TrendingUp as p } from "lucide-react";
|
|
14
|
+
import { gql as m } from "@apollo/client";
|
|
15
|
+
import { useQuery as h } from "@apollo/client/react";
|
|
16
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
17
|
+
import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
18
|
+
import { Card as b, CardContent as x, CardHeader as S, CardTitle as C } from "@burdenoff/fe-libs/ui";
|
|
18
19
|
//#region src/planmagnet/pages/analytics/SprintDeliveryCards.tsx
|
|
19
|
-
var
|
|
20
|
+
var w = m`
|
|
20
21
|
query DashboardSprintBurndown($workspaceId: ID!, $sprintId: ID!) {
|
|
21
22
|
planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {
|
|
22
23
|
sprintId
|
|
@@ -29,7 +30,7 @@ var C = p`
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
`,
|
|
33
|
+
`, T = m`
|
|
33
34
|
query DashboardVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {
|
|
34
35
|
planmagnetVelocity(
|
|
35
36
|
workspaceId: $workspaceId
|
|
@@ -39,6 +40,11 @@ var C = p`
|
|
|
39
40
|
projectId
|
|
40
41
|
averageVelocity
|
|
41
42
|
forecastNextSprintPoints
|
|
43
|
+
forecastRange {
|
|
44
|
+
p15
|
|
45
|
+
p85
|
|
46
|
+
}
|
|
47
|
+
insufficientHistory
|
|
42
48
|
sprints {
|
|
43
49
|
sprintId
|
|
44
50
|
sprintName
|
|
@@ -49,171 +55,192 @@ var C = p`
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
`;
|
|
52
|
-
function
|
|
58
|
+
function E(e) {
|
|
53
59
|
let t = new Date(e);
|
|
54
60
|
return `${t.getUTCMonth() + 1}/${t.getUTCDate()}`;
|
|
55
61
|
}
|
|
56
|
-
function
|
|
57
|
-
let { t:
|
|
62
|
+
function D({ workspaceId: r, sprintId: i }) {
|
|
63
|
+
let { t: a } = y(), { data: d, loading: p, error: m } = h(w, {
|
|
58
64
|
variables: {
|
|
59
65
|
workspaceId: r,
|
|
60
66
|
sprintId: i
|
|
61
67
|
},
|
|
62
68
|
skip: !r || !i
|
|
63
|
-
}),
|
|
64
|
-
day:
|
|
69
|
+
}), g = (d?.planmagnetSprintBurndown?.points ?? []).map((e) => ({
|
|
70
|
+
day: E(e.date),
|
|
65
71
|
remaining: e.remainingPoints,
|
|
66
72
|
completed: e.completedPoints
|
|
67
73
|
}));
|
|
68
|
-
return /* @__PURE__ */
|
|
74
|
+
return /* @__PURE__ */ v(b, { children: [/* @__PURE__ */ v(S, { children: [/* @__PURE__ */ v(C, {
|
|
69
75
|
className: "flex items-center gap-2",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
71
|
-
}), /* @__PURE__ */
|
|
76
|
+
children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), a("planmagnet.dashboards.burndown.title", "Sprint burndown")]
|
|
77
|
+
}), /* @__PURE__ */ _("p", {
|
|
72
78
|
className: "text-xs text-muted-foreground",
|
|
73
|
-
children:
|
|
74
|
-
})] }), /* @__PURE__ */
|
|
79
|
+
children: a("planmagnet.dashboards.burndown.subtitle", "Remaining vs. completed story points, by day of the sprint.")
|
|
80
|
+
})] }), /* @__PURE__ */ _(x, { children: i ? p && !d ? /* @__PURE__ */ _("p", {
|
|
75
81
|
className: "text-sm text-muted-foreground",
|
|
76
|
-
children:
|
|
77
|
-
}) :
|
|
82
|
+
children: a("planmagnet.dashboards.loading", "Loading…")
|
|
83
|
+
}) : m ? /* @__PURE__ */ v("p", {
|
|
78
84
|
className: "text-sm text-status-error-text",
|
|
79
85
|
children: [
|
|
80
|
-
|
|
86
|
+
a("planmagnet.dashboards.burndown.error", "Burndown could not be loaded"),
|
|
81
87
|
":",
|
|
82
88
|
" ",
|
|
83
|
-
|
|
89
|
+
m.message
|
|
84
90
|
]
|
|
85
|
-
}) :
|
|
91
|
+
}) : g.length === 0 ? /* @__PURE__ */ _("p", {
|
|
86
92
|
className: "text-sm text-muted-foreground",
|
|
87
|
-
children:
|
|
88
|
-
}) : /* @__PURE__ */
|
|
93
|
+
children: a("planmagnet.dashboards.burndown.empty", "The active sprint has no work items yet.")
|
|
94
|
+
}) : /* @__PURE__ */ _(n, {
|
|
89
95
|
width: "100%",
|
|
90
96
|
height: 220,
|
|
91
|
-
children: /* @__PURE__ */
|
|
92
|
-
data:
|
|
97
|
+
children: /* @__PURE__ */ v(u, {
|
|
98
|
+
data: g,
|
|
93
99
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ _(o, {
|
|
95
101
|
strokeDasharray: "3 3",
|
|
96
102
|
opacity: .2
|
|
97
103
|
}),
|
|
98
|
-
/* @__PURE__ */
|
|
104
|
+
/* @__PURE__ */ _(c, {
|
|
99
105
|
dataKey: "day",
|
|
100
106
|
tick: { fontSize: 11 }
|
|
101
107
|
}),
|
|
102
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ _(l, {
|
|
103
109
|
tick: { fontSize: 11 },
|
|
104
110
|
allowDecimals: !1
|
|
105
111
|
}),
|
|
106
|
-
/* @__PURE__ */
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ _(t, {}),
|
|
113
|
+
/* @__PURE__ */ _(e, { wrapperStyle: { fontSize: 12 } }),
|
|
114
|
+
/* @__PURE__ */ _(s, {
|
|
109
115
|
type: "monotone",
|
|
110
116
|
dataKey: "remaining",
|
|
111
|
-
name:
|
|
117
|
+
name: a("planmagnet.dashboards.burndown.remaining", "Remaining points"),
|
|
112
118
|
stroke: "#f59e0b",
|
|
113
119
|
strokeWidth: 2,
|
|
114
120
|
dot: { r: 2 }
|
|
115
121
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ _(s, {
|
|
117
123
|
type: "monotone",
|
|
118
124
|
dataKey: "completed",
|
|
119
|
-
name:
|
|
125
|
+
name: a("planmagnet.dashboards.burndown.completed", "Completed points"),
|
|
120
126
|
stroke: "#22c55e",
|
|
121
127
|
strokeWidth: 2,
|
|
122
128
|
dot: { r: 2 }
|
|
123
129
|
})
|
|
124
130
|
]
|
|
125
131
|
})
|
|
126
|
-
}) : /* @__PURE__ */
|
|
132
|
+
}) : /* @__PURE__ */ _("p", {
|
|
127
133
|
className: "text-sm text-muted-foreground",
|
|
128
|
-
children:
|
|
134
|
+
children: a("planmagnet.dashboards.burndown.noSprint", "No active sprint for this project.")
|
|
129
135
|
}) })] });
|
|
130
136
|
}
|
|
131
|
-
function
|
|
132
|
-
let { t:
|
|
137
|
+
function O({ workspaceId: s, projectId: u, trailingSprintCount: f = 6 }) {
|
|
138
|
+
let { t: m } = y(), { data: w, loading: E, error: D } = h(T, {
|
|
133
139
|
variables: {
|
|
134
|
-
workspaceId:
|
|
135
|
-
projectId:
|
|
136
|
-
trailingSprintCount:
|
|
140
|
+
workspaceId: s,
|
|
141
|
+
projectId: u,
|
|
142
|
+
trailingSprintCount: f
|
|
137
143
|
},
|
|
138
|
-
skip: !
|
|
139
|
-
}),
|
|
144
|
+
skip: !s || !u
|
|
145
|
+
}), O = w?.planmagnetVelocity, k = O?.sprints.map((e) => ({
|
|
140
146
|
name: e.sprintName,
|
|
141
147
|
completed: e.completedPoints,
|
|
142
148
|
planned: e.plannedPoints ?? void 0
|
|
143
|
-
})) ?? [],
|
|
144
|
-
return /* @__PURE__ */
|
|
149
|
+
})) ?? [], A = O ? Math.round(O.averageVelocity) : 0, j = O ? Math.round(O.forecastNextSprintPoints) : 0, M = O?.forecastRange ?? null, N = O?.insufficientHistory ?? !1;
|
|
150
|
+
return /* @__PURE__ */ v(b, { children: [/* @__PURE__ */ v(S, { children: [/* @__PURE__ */ v(C, {
|
|
145
151
|
className: "flex items-center gap-2",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
147
|
-
}), /* @__PURE__ */
|
|
152
|
+
children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), m("planmagnet.dashboards.velocity.title", "Sprint velocity")]
|
|
153
|
+
}), /* @__PURE__ */ _("p", {
|
|
148
154
|
className: "text-xs text-muted-foreground",
|
|
149
|
-
children:
|
|
150
|
-
})] }), /* @__PURE__ */
|
|
155
|
+
children: m("planmagnet.dashboards.velocity.subtitle", "Completed story points per sprint, across the trailing {{count}} completed sprints.", { count: f })
|
|
156
|
+
})] }), /* @__PURE__ */ _(x, { children: u ? E && !w ? /* @__PURE__ */ _("p", {
|
|
151
157
|
className: "text-sm text-muted-foreground",
|
|
152
|
-
children:
|
|
153
|
-
}) :
|
|
158
|
+
children: m("planmagnet.dashboards.loading", "Loading…")
|
|
159
|
+
}) : D ? /* @__PURE__ */ v("p", {
|
|
154
160
|
className: "text-sm text-status-error-text",
|
|
155
161
|
children: [
|
|
156
|
-
|
|
162
|
+
m("planmagnet.dashboards.velocity.error", "Velocity could not be loaded"),
|
|
157
163
|
":",
|
|
158
164
|
" ",
|
|
159
|
-
|
|
165
|
+
D.message
|
|
160
166
|
]
|
|
161
|
-
}) :
|
|
167
|
+
}) : k.length === 0 ? /* @__PURE__ */ _("p", {
|
|
162
168
|
className: "text-sm text-muted-foreground",
|
|
163
|
-
children:
|
|
164
|
-
}) : /* @__PURE__ */
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
169
|
+
children: m("planmagnet.dashboards.velocity.empty", "No completed sprints yet for this project.")
|
|
170
|
+
}) : /* @__PURE__ */ v(g, { children: [
|
|
171
|
+
/* @__PURE__ */ _(n, {
|
|
172
|
+
width: "100%",
|
|
173
|
+
height: 190,
|
|
174
|
+
children: /* @__PURE__ */ v(d, {
|
|
175
|
+
data: k,
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ _(o, {
|
|
178
|
+
strokeDasharray: "3 3",
|
|
179
|
+
opacity: .2
|
|
180
|
+
}),
|
|
181
|
+
/* @__PURE__ */ _(c, {
|
|
182
|
+
dataKey: "name",
|
|
183
|
+
tick: { fontSize: 11 }
|
|
184
|
+
}),
|
|
185
|
+
/* @__PURE__ */ _(l, {
|
|
186
|
+
tick: { fontSize: 11 },
|
|
187
|
+
allowDecimals: !1
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ _(t, {}),
|
|
190
|
+
/* @__PURE__ */ _(e, { wrapperStyle: { fontSize: 12 } }),
|
|
191
|
+
M && /* @__PURE__ */ _(a, {
|
|
192
|
+
y1: M.p15,
|
|
193
|
+
y2: M.p85,
|
|
194
|
+
fill: "var(--color-chart-2)",
|
|
195
|
+
fillOpacity: .12,
|
|
196
|
+
stroke: "none"
|
|
197
|
+
}),
|
|
198
|
+
/* @__PURE__ */ _(r, {
|
|
199
|
+
dataKey: "planned",
|
|
200
|
+
name: m("planmagnet.dashboards.velocity.planned", "Planned"),
|
|
201
|
+
fill: "#cbd5e1"
|
|
202
|
+
}),
|
|
203
|
+
/* @__PURE__ */ _(r, {
|
|
204
|
+
dataKey: "completed",
|
|
205
|
+
name: m("planmagnet.dashboards.velocity.completed", "Completed"),
|
|
206
|
+
fill: "#6366f1"
|
|
207
|
+
}),
|
|
208
|
+
/* @__PURE__ */ _(i, {
|
|
209
|
+
y: A,
|
|
210
|
+
stroke: "#22c55e",
|
|
211
|
+
strokeDasharray: "4 4",
|
|
212
|
+
label: {
|
|
213
|
+
value: m("planmagnet.dashboards.velocity.avgLabel", "avg"),
|
|
214
|
+
fontSize: 10
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
})
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ v("p", {
|
|
221
|
+
className: "mt-2 text-center text-xs text-muted-foreground",
|
|
222
|
+
children: [m("planmagnet.dashboards.velocity.trailingAverage", "Median of the last {{count}} completed sprints: {{points}} pts per sprint", {
|
|
223
|
+
count: k.length,
|
|
224
|
+
points: j
|
|
225
|
+
}), M && ` · ${m("planmagnet.dashboards.velocity.range", "likely range (p15–p85): {{low}}–{{high}} pts", {
|
|
226
|
+
low: Math.round(M.p15),
|
|
227
|
+
high: Math.round(M.p85)
|
|
228
|
+
})}`]
|
|
229
|
+
}),
|
|
230
|
+
N && /* @__PURE__ */ _("p", {
|
|
231
|
+
className: "mt-1 text-center text-xs text-status-warning-text",
|
|
232
|
+
children: m("planmagnet.dashboards.velocity.insufficientHistory", "Fewer than 3 sprints with completed points — this forecast is low-confidence.")
|
|
233
|
+
}),
|
|
234
|
+
/* @__PURE__ */ _("p", {
|
|
235
|
+
className: "mt-1 text-center text-xs text-muted-foreground",
|
|
236
|
+
children: m("planmagnet.dashboards.velocity.methodLabel", "Statistical forecast (not AI-generated).")
|
|
210
237
|
})
|
|
211
|
-
|
|
238
|
+
] }) : /* @__PURE__ */ _("p", {
|
|
212
239
|
className: "text-sm text-muted-foreground",
|
|
213
|
-
children:
|
|
240
|
+
children: m("planmagnet.dashboards.velocity.noProject", "Select a project to see its velocity.")
|
|
214
241
|
}) })] });
|
|
215
242
|
}
|
|
216
243
|
//#endregion
|
|
217
|
-
export {
|
|
244
|
+
export { D as SprintBurndownCard, O as SprintVelocityCard };
|
|
218
245
|
|
|
219
246
|
//# sourceMappingURL=SprintDeliveryCards.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SprintDeliveryCards.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/SprintDeliveryCards.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, TrendingUp } from 'lucide-react';\nimport {\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\n\n/**\n * Sprint burndown and velocity, as their own cards (BOFF-4447's server-computed\n * `planmagnetSprintBurndown` / `planmagnetVelocity`). Extracted out of\n * `pages/AnalyticsAnalyticsPagePage.tsx`, where this same chart code lived inline, so the\n * role dashboards (`dashboards/project-manager-page`) can import the chart rather than\n * duplicating it — same reasoning as `CycleTimeCard`/`BudgetPerformanceCard` alongside\n * them in `DeliveryHealthCards.tsx`. Each card owns its query, so neither can land a hook\n * after an early return in the host page.\n *\n * recharts' `stroke`/`fill` render as literal SVG paint attributes, not Tailwind\n * classNames — they cannot consume semantic `bg-*`/`text-*` tokens, the same documented\n * exception `AnalyticsAnalyticsPagePage.tsx`'s cumulative-flow chart carries.\n */\n\nconst GET_SPRINT_BURNDOWN = gql`\n query DashboardSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n completedPoints\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query DashboardVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n plannedPoints\n }\n }\n }\n`;\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n totalPoints: number;\n totalCount: number;\n points: { date: string; remainingPoints: number; completedPoints: number }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function SprintBurndownCard({\n workspaceId,\n sprintId,\n}: {\n workspaceId?: string | null;\n sprintId: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<BurndownData>(GET_SPRINT_BURNDOWN, {\n variables: { workspaceId, sprintId },\n skip: !workspaceId || !sprintId,\n });\n const points = data?.planmagnetSprintBurndown?.points ?? [];\n const chartData = points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n }));\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.dashboards.burndown.title', 'Sprint burndown')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.burndown.subtitle',\n 'Remaining vs. completed story points, by day of the sprint.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!sprintId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.burndown.noSprint', 'No active sprint for this project.')}\n </p>\n ) : loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.dashboards.burndown.error', 'Burndown could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : chartData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.burndown.empty', 'The active sprint has no work items yet.')}\n </p>\n ) : (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.dashboards.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.dashboards.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport function SprintVelocityCard({\n workspaceId,\n projectId,\n trailingSprintCount = 6,\n}: {\n workspaceId?: string | null;\n projectId: string;\n trailingSprintCount?: number;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId, trailingSprintCount },\n skip: !workspaceId || !projectId,\n });\n const velocity = data?.planmagnetVelocity;\n const chartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.dashboards.velocity.title', 'Sprint velocity')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.subtitle',\n 'Completed story points per sprint, across the trailing {{count}} completed sprints.',\n { count: trailingSprintCount }\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!projectId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.velocity.noProject', 'Select a project to see its velocity.')}\n </p>\n ) : loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.dashboards.velocity.error', 'Velocity could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : chartData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.empty',\n 'No completed sprints yet for this project.'\n )}\n </p>\n ) : (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.dashboards.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.dashboards.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.dashboards.velocity.avgLabel', 'avg'),\n fontSize: 10,\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"mt-2 text-center text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.trailingAverage',\n 'Average of the last {{count}} completed sprints: {{points}} pts per sprint',\n { count: chartData.length, points: avgVelocity }\n )}\n </p>\n </>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SprintDeliveryCards.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/SprintDeliveryCards.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, TrendingUp } from 'lucide-react';\nimport {\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceArea,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\n\n/**\n * Sprint burndown and velocity, as their own cards (BOFF-4447's server-computed\n * `planmagnetSprintBurndown` / `planmagnetVelocity`). Extracted out of\n * `pages/AnalyticsAnalyticsPagePage.tsx`, where this same chart code lived inline, so the\n * role dashboards (`dashboards/project-manager-page`) can import the chart rather than\n * duplicating it — same reasoning as `CycleTimeCard`/`BudgetPerformanceCard` alongside\n * them in `DeliveryHealthCards.tsx`. Each card owns its query, so neither can land a hook\n * after an early return in the host page.\n *\n * recharts' `stroke`/`fill` render as literal SVG paint attributes, not Tailwind\n * classNames — they cannot consume semantic `bg-*`/`text-*` tokens, the same documented\n * exception `AnalyticsAnalyticsPagePage.tsx`'s cumulative-flow chart carries.\n */\n\nconst GET_SPRINT_BURNDOWN = gql`\n query DashboardSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n completedPoints\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query DashboardVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n forecastRange {\n p15\n p85\n }\n insufficientHistory\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n plannedPoints\n }\n }\n }\n`;\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n totalPoints: number;\n totalCount: number;\n points: { date: string; remainingPoints: number; completedPoints: number }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n forecastRange: { p15: number; p85: number } | null;\n insufficientHistory: boolean;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function SprintBurndownCard({\n workspaceId,\n sprintId,\n}: {\n workspaceId?: string | null;\n sprintId: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<BurndownData>(GET_SPRINT_BURNDOWN, {\n variables: { workspaceId, sprintId },\n skip: !workspaceId || !sprintId,\n });\n const points = data?.planmagnetSprintBurndown?.points ?? [];\n const chartData = points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n }));\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.dashboards.burndown.title', 'Sprint burndown')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.burndown.subtitle',\n 'Remaining vs. completed story points, by day of the sprint.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!sprintId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.burndown.noSprint', 'No active sprint for this project.')}\n </p>\n ) : loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.dashboards.burndown.error', 'Burndown could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : chartData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.burndown.empty', 'The active sprint has no work items yet.')}\n </p>\n ) : (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.dashboards.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.dashboards.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport function SprintVelocityCard({\n workspaceId,\n projectId,\n trailingSprintCount = 6,\n}: {\n workspaceId?: string | null;\n projectId: string;\n trailingSprintCount?: number;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId, trailingSprintCount },\n skip: !workspaceId || !projectId,\n });\n const velocity = data?.planmagnetVelocity;\n const chartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n const forecastNextSprint = velocity ? Math.round(velocity.forecastNextSprintPoints) : 0;\n const forecastRange = velocity?.forecastRange ?? null;\n const insufficientHistory = velocity?.insufficientHistory ?? false;\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.dashboards.velocity.title', 'Sprint velocity')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.subtitle',\n 'Completed story points per sprint, across the trailing {{count}} completed sprints.',\n { count: trailingSprintCount }\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!projectId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.velocity.noProject', 'Select a project to see its velocity.')}\n </p>\n ) : loading && !data ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.dashboards.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.dashboards.velocity.error', 'Velocity could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : chartData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.empty',\n 'No completed sprints yet for this project.'\n )}\n </p>\n ) : (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={chartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {/* ANR-02: the empirical p15-p85 band around next sprint's forecast — full-width (no\n x1/x2) since it isn't tied to any historical sprint's x position. */}\n {forecastRange && (\n <ReferenceArea\n y1={forecastRange.p15}\n y2={forecastRange.p85}\n fill=\"var(--color-chart-2)\"\n fillOpacity={0.12}\n stroke=\"none\"\n />\n )}\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.dashboards.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.dashboards.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.dashboards.velocity.avgLabel', 'avg'),\n fontSize: 10,\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n {/* Accessible text restatement of the shaded band, for screen readers. */}\n <p className=\"mt-2 text-center text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.trailingAverage',\n 'Median of the last {{count}} completed sprints: {{points}} pts per sprint',\n { count: chartData.length, points: forecastNextSprint }\n )}\n {forecastRange &&\n ` · ${t(\n 'planmagnet.dashboards.velocity.range',\n 'likely range (p15–p85): {{low}}–{{high}} pts',\n { low: Math.round(forecastRange.p15), high: Math.round(forecastRange.p85) }\n )}`}\n </p>\n {insufficientHistory && (\n <p className=\"mt-1 text-center text-xs text-status-warning-text\">\n {t(\n 'planmagnet.dashboards.velocity.insufficientHistory',\n 'Fewer than 3 sprints with completed points — this forecast is low-confidence.'\n )}\n </p>\n )}\n <p className=\"mt-1 text-center text-xs text-muted-foreground\">\n {t(\n 'planmagnet.dashboards.velocity.methodLabel',\n 'Statistical forecast (not AI-generated).'\n )}\n </p>\n </>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Tooltip as e } from "../../../node_modules/recharts/es6/component/Tooltip.js";
|
|
2
2
|
import { ResponsiveContainer as t } from "../../../node_modules/recharts/es6/component/ResponsiveContainer.js";
|
|
3
3
|
import { Sankey as n } from "../../../node_modules/recharts/es6/chart/Sankey.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { isForbidden as r } from "../../utils/graphqlErrors.js";
|
|
5
|
+
import { WORK_ITEM_STATUSES as i, enumLabel as a } from "../../controls/WorkItemControls.js";
|
|
6
|
+
import { statusVisual as o } from "../../utils/statusColor.js";
|
|
7
7
|
import { useMemo as s } from "react";
|
|
8
8
|
import { Workflow as c } from "lucide-react";
|
|
9
9
|
import { gql as l } from "@apollo/client";
|
|
@@ -39,19 +39,19 @@ function x(e, t) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
function S(e) {
|
|
42
|
-
let t =
|
|
42
|
+
let t = i.filter((t) => e.some((e) => e.fromStatus === t)), n = i.filter((t) => e.some((e) => e.toStatus === t)), r = new Map(t.map((e, t) => [e, t])), o = new Map(n.map((e, n) => [e, t.length + n]));
|
|
43
43
|
return {
|
|
44
44
|
nodes: [...t.map((e) => ({
|
|
45
|
-
name:
|
|
45
|
+
name: a(e),
|
|
46
46
|
status: e,
|
|
47
47
|
side: "from"
|
|
48
48
|
})), ...n.map((e) => ({
|
|
49
|
-
name:
|
|
49
|
+
name: a(e),
|
|
50
50
|
status: e,
|
|
51
51
|
side: "to"
|
|
52
52
|
}))],
|
|
53
53
|
links: e.flatMap((e) => {
|
|
54
|
-
let t =
|
|
54
|
+
let t = r.get(e.fromStatus), n = o.get(e.toStatus);
|
|
55
55
|
return t === void 0 || n === void 0 ? [] : [{
|
|
56
56
|
source: t,
|
|
57
57
|
target: n,
|
|
@@ -61,7 +61,7 @@ function S(e) {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
function C({ x: e, y: t, width: n, height: r, payload: i }) {
|
|
64
|
-
let
|
|
64
|
+
let a = o(i.status), s = i.side === "from";
|
|
65
65
|
return /* @__PURE__ */ f("g", { children: [/* @__PURE__ */ d("rect", {
|
|
66
66
|
x: e,
|
|
67
67
|
y: t,
|
|
@@ -69,8 +69,8 @@ function C({ x: e, y: t, width: n, height: r, payload: i }) {
|
|
|
69
69
|
height: r,
|
|
70
70
|
rx: 2,
|
|
71
71
|
style: {
|
|
72
|
-
fill:
|
|
73
|
-
stroke:
|
|
72
|
+
fill: a.color,
|
|
73
|
+
stroke: a.color
|
|
74
74
|
},
|
|
75
75
|
strokeWidth: 1
|
|
76
76
|
}), /* @__PURE__ */ d("text", {
|
|
@@ -82,25 +82,25 @@ function C({ x: e, y: t, width: n, height: r, payload: i }) {
|
|
|
82
82
|
children: i.name
|
|
83
83
|
})] });
|
|
84
84
|
}
|
|
85
|
-
function w({ sourceX: e, sourceY: t, targetX: n, targetY: r, sourceControlX: i, targetControlX:
|
|
86
|
-
let l =
|
|
85
|
+
function w({ sourceX: e, sourceY: t, targetX: n, targetY: r, sourceControlX: i, targetControlX: a, linkWidth: s, payload: c }) {
|
|
86
|
+
let l = o(c.source.status);
|
|
87
87
|
return /* @__PURE__ */ d("path", {
|
|
88
|
-
d: `M${e},${t} C${i},${t} ${
|
|
88
|
+
d: `M${e},${t} C${i},${t} ${a},${r} ${n},${r}`,
|
|
89
89
|
style: { stroke: l.color },
|
|
90
90
|
strokeWidth: Math.max(s, 1),
|
|
91
91
|
strokeOpacity: .45,
|
|
92
92
|
fill: "none"
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
function T({ workspaceId:
|
|
95
|
+
function T({ workspaceId: i, projectId: o, startDate: l, endDate: b }) {
|
|
96
96
|
let { t: T } = p(), E = s(() => x(l, b), [l, b]), { data: D, loading: O, error: k } = u(v, {
|
|
97
97
|
variables: {
|
|
98
|
-
workspaceId:
|
|
99
|
-
projectId:
|
|
98
|
+
workspaceId: i,
|
|
99
|
+
projectId: o,
|
|
100
100
|
from: E.from,
|
|
101
101
|
to: E.to
|
|
102
102
|
},
|
|
103
|
-
skip: !
|
|
103
|
+
skip: !i || !o
|
|
104
104
|
}), A = D?.planmagnetStatusFlow ?? [], { nodes: j, links: M } = S(A), N = [...A].sort((e, t) => t.count - e.count);
|
|
105
105
|
return /* @__PURE__ */ f(m, {
|
|
106
106
|
"data-testid": "status-flow-card",
|
|
@@ -120,12 +120,12 @@ function T({ workspaceId: r, projectId: a, startDate: l, endDate: b }) {
|
|
|
120
120
|
from: E.from.slice(0, 10)
|
|
121
121
|
})
|
|
122
122
|
})
|
|
123
|
-
] }), /* @__PURE__ */ d(h, { children:
|
|
123
|
+
] }), /* @__PURE__ */ d(h, { children: o ? O && !D ? /* @__PURE__ */ d("div", {
|
|
124
124
|
className: "flex justify-center py-12",
|
|
125
125
|
children: /* @__PURE__ */ d("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
|
|
126
126
|
}) : k ? /* @__PURE__ */ d("p", {
|
|
127
127
|
className: "text-sm text-status-error-text",
|
|
128
|
-
children:
|
|
128
|
+
children: r(k) ? T("planmagnet.analytics.statusFlow.forbidden", "You do not have access to status flow for this project.") : `${T("planmagnet.analytics.statusFlow.error", "Status flow could not be loaded")}: ${k.message}`
|
|
129
129
|
}) : A.length === 0 ? /* @__PURE__ */ d("p", {
|
|
130
130
|
className: "text-sm text-muted-foreground py-8 text-center",
|
|
131
131
|
children: T("planmagnet.analytics.statusFlow.empty", "No status changes recorded for this project in the selected range.")
|
|
@@ -208,11 +208,11 @@ function T({ workspaceId: r, projectId: a, startDate: l, endDate: b }) {
|
|
|
208
208
|
children: [
|
|
209
209
|
/* @__PURE__ */ d("td", {
|
|
210
210
|
className: "py-1.5 pr-2",
|
|
211
|
-
children:
|
|
211
|
+
children: a(e.fromStatus)
|
|
212
212
|
}),
|
|
213
213
|
/* @__PURE__ */ d("td", {
|
|
214
214
|
className: "py-1.5 pr-2",
|
|
215
|
-
children:
|
|
215
|
+
children: a(e.toStatus)
|
|
216
216
|
}),
|
|
217
217
|
/* @__PURE__ */ d("td", {
|
|
218
218
|
className: "py-1.5 text-right tabular-nums",
|