@burdenoff/microfe-planmagnet 2026.924.3 → 2026.924.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"names":[],"mappings":"AAqQA,wBAAgB,0BAA0B,4CAiczC"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"names":[],"mappings":"AAsQA,wBAAgB,0BAA0B,4CAsczC"}
@@ -6,21 +6,22 @@ import { Bar as i } from "../../node_modules/recharts/es6/cartesian/Bar.js";
6
6
  import { ReferenceLine as ee } from "../../node_modules/recharts/es6/cartesian/ReferenceLine.js";
7
7
  import { CartesianGrid as a } from "../../node_modules/recharts/es6/cartesian/CartesianGrid.js";
8
8
  import { Line as o } from "../../node_modules/recharts/es6/cartesian/Line.js";
9
- import { Area as s } from "../../node_modules/recharts/es6/cartesian/Area.js";
10
- import { XAxis as c } from "../../node_modules/recharts/es6/cartesian/XAxis.js";
11
- import { YAxis as l } from "../../node_modules/recharts/es6/cartesian/YAxis.js";
12
- import { LineChart as te } from "../../node_modules/recharts/es6/chart/LineChart.js";
13
- import { BarChart as ne } from "../../node_modules/recharts/es6/chart/BarChart.js";
14
- import { AreaChart as u } from "../../node_modules/recharts/es6/chart/AreaChart.js";
9
+ import { Area as te } from "../../node_modules/recharts/es6/cartesian/Area.js";
10
+ import { XAxis as s } from "../../node_modules/recharts/es6/cartesian/XAxis.js";
11
+ import { YAxis as c } from "../../node_modules/recharts/es6/cartesian/YAxis.js";
12
+ import { LineChart as ne } from "../../node_modules/recharts/es6/chart/LineChart.js";
13
+ import { BarChart as re } from "../../node_modules/recharts/es6/chart/BarChart.js";
14
+ import { AreaChart as ie } from "../../node_modules/recharts/es6/chart/AreaChart.js";
15
+ import { BudgetPerformanceCard as l, CycleTimeCard as u } from "./analytics/DeliveryHealthCards.js";
15
16
  import { useState as d } from "react";
16
- import { BarChart3 as f, FolderKanban as re, Target as ie, TrendingUp as p, Zap as m } from "lucide-react";
17
- import { gql as h } from "@apollo/client";
18
- import { useQuery as g } from "@apollo/client/react";
19
- import { Fragment as ae, jsx as _, jsxs as v } from "react/jsx-runtime";
17
+ import { BarChart3 as f, FolderKanban as p, Target as m, TrendingUp as h, Zap as ae } from "lucide-react";
18
+ import { gql as g } from "@apollo/client";
19
+ import { useQuery as _ } from "@apollo/client/react";
20
+ import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
20
21
  import { useI18n as oe } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
21
- import { Badge as se, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Select as C, SelectContent as w, SelectItem as T, SelectTrigger as E, SelectValue as D } from "@burdenoff/fe-libs/ui";
22
+ import { Badge as se, Card as x, CardContent as S, CardHeader as C, CardTitle as w, Select as T, SelectContent as E, SelectItem as D, SelectTrigger as O, SelectValue as k } from "@burdenoff/fe-libs/ui";
22
23
  //#region src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx
23
- var ce = h`
24
+ var A = g`
24
25
  query GetAnalyticsData(
25
26
  $workspaceId: ID!
26
27
  $projectPagination: PlanmagnetPaginationInput
@@ -73,7 +74,7 @@ var ce = h`
73
74
  totalCount
74
75
  }
75
76
  }
76
- `, le = h`
77
+ `, j = g`
77
78
  query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {
78
79
  planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {
79
80
  sprintId
@@ -90,7 +91,7 @@ var ce = h`
90
91
  }
91
92
  }
92
93
  }
93
- `, O = h`
94
+ `, ce = g`
94
95
  query GetVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {
95
96
  planmagnetVelocity(
96
97
  workspaceId: $workspaceId
@@ -110,7 +111,7 @@ var ce = h`
110
111
  }
111
112
  }
112
113
  }
113
- `, k = h`
114
+ `, le = g`
114
115
  query GetCumulativeFlow($workspaceId: ID!, $projectId: ID!) {
115
116
  planmagnetCumulativeFlow(workspaceId: $workspaceId, projectId: $projectId) {
116
117
  projectId
@@ -125,7 +126,7 @@ var ce = h`
125
126
  }
126
127
  }
127
128
  }
128
- `, A = {
129
+ `, M = {
129
130
  BACKLOG: "#9ca3af",
130
131
  TODO: "#60a5fa",
131
132
  IN_PROGRESS: "#fbbf24",
@@ -134,396 +135,403 @@ var ce = h`
134
135
  DONE: "#4ade80",
135
136
  CANCELLED: "#f87171"
136
137
  };
137
- function j(e) {
138
+ function N(e) {
138
139
  let t = new Date(e);
139
140
  return `${t.getUTCMonth() + 1}/${t.getUTCDate()}`;
140
141
  }
141
- function M() {
142
- let { workspaceId: h } = e(), { t: M } = oe(), [ue, de] = d(""), [fe, N] = d(""), { data: P, loading: F, error: I } = g(ce, {
142
+ function P() {
143
+ let { workspaceId: g } = e(), { t: P } = oe(), [ue, de] = d(""), [fe, F] = d(""), { data: I, loading: L, error: R } = _(A, {
143
144
  variables: {
144
- workspaceId: h,
145
+ workspaceId: g,
145
146
  projectPagination: { first: 100 },
146
147
  workItemPagination: { first: 500 },
147
148
  sprintPagination: { first: 100 },
148
149
  milestonePagination: { first: 100 }
149
150
  },
150
- skip: !h
151
- }), L = P?.planmagnetProjects?.items ?? [], R = P?.planmagnetWorkItems?.items ?? [], z = P?.planmagnetSprints?.items ?? [], B = P?.planmagnetMilestones?.items ?? [], V = ue || L[0]?.id || "", H = z.filter((e) => e.projectId === V), U = fe || H[0]?.id || "", { data: pe, loading: me } = g(le, {
151
+ skip: !g
152
+ }), z = I?.planmagnetProjects?.items ?? [], B = I?.planmagnetWorkItems?.items ?? [], V = I?.planmagnetSprints?.items ?? [], pe = I?.planmagnetMilestones?.items ?? [], H = ue || z[0]?.id || "", U = V.filter((e) => e.projectId === H), W = fe || U[0]?.id || "", { data: me, loading: he } = _(j, {
152
153
  variables: {
153
- workspaceId: h,
154
- sprintId: U
154
+ workspaceId: g,
155
+ sprintId: W
155
156
  },
156
- skip: !h || !U
157
- }), { data: he, loading: ge } = g(O, {
157
+ skip: !g || !W
158
+ }), { data: ge, loading: _e } = _(ce, {
158
159
  variables: {
159
- workspaceId: h,
160
- projectId: V,
160
+ workspaceId: g,
161
+ projectId: H,
161
162
  trailingSprintCount: 6
162
163
  },
163
- skip: !h || !V
164
- }), { data: _e, loading: ve } = g(k, {
164
+ skip: !g || !H
165
+ }), { data: ve, loading: ye } = _(le, {
165
166
  variables: {
166
- workspaceId: h,
167
- projectId: V
167
+ workspaceId: g,
168
+ projectId: H
168
169
  },
169
- skip: !h || !V
170
+ skip: !g || !H
170
171
  });
171
- if (F) return /* @__PURE__ */ _("div", {
172
+ if (L) return /* @__PURE__ */ y("div", {
172
173
  className: "flex items-center justify-center p-12",
173
- children: /* @__PURE__ */ _("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
174
+ children: /* @__PURE__ */ y("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
174
175
  });
175
- if (I) return /* @__PURE__ */ v("div", {
176
+ if (R) return /* @__PURE__ */ b("div", {
176
177
  className: "p-6 text-red-500",
177
178
  children: [
178
- M("planmagnet.analytics.errorLoading", "Error loading analytics"),
179
+ P("planmagnet.analytics.errorLoading", "Error loading analytics"),
179
180
  ": ",
180
- I.message
181
+ R.message
181
182
  ]
182
183
  });
183
- let W = R.length, ye = R.filter((e) => e.status === "DONE").length, G = R.filter((e) => e.status === "BLOCKED").length, be = R.reduce((e, t) => e + (t.storyPoints ?? 0), 0), xe = R.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0), K = z.filter((e) => e.status === "ACTIVE").length, Se = B.filter((e) => e.status === "AT_RISK" || e.status === "OVERDUE").length, q = L.filter((e) => e.status === "ACTIVE").length, Ce = W > 0 ? Math.round(ye / W * 100) : 0, J = (pe?.planmagnetSprintBurndown)?.points.map((e) => ({
184
- day: j(e.date),
184
+ let G = B.length, be = B.filter((e) => e.status === "DONE").length, K = B.filter((e) => e.status === "BLOCKED").length, xe = B.reduce((e, t) => e + (t.storyPoints ?? 0), 0), Se = B.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0), Ce = V.filter((e) => e.status === "ACTIVE").length, we = pe.filter((e) => e.status === "AT_RISK" || e.status === "OVERDUE").length, q = z.filter((e) => e.status === "ACTIVE").length, Te = G > 0 ? Math.round(be / G * 100) : 0, J = (me?.planmagnetSprintBurndown)?.points.map((e) => ({
185
+ day: N(e.date),
185
186
  remaining: e.remainingPoints,
186
187
  completed: e.completedPoints
187
- })) ?? [], Y = he?.planmagnetVelocity, X = Y?.sprints.map((e) => ({
188
+ })) ?? [], Y = ge?.planmagnetVelocity, X = Y?.sprints.map((e) => ({
188
189
  name: e.sprintName,
189
190
  completed: e.completedPoints,
190
191
  planned: e.plannedPoints ?? void 0
191
- })) ?? [], Z = Y ? Math.round(Y.averageVelocity) : 0, we = Y ? Math.round(Y.forecastNextSprintPoints) : 0, Q = _e?.planmagnetCumulativeFlow, Te = Array.from(new Set(Q?.points.flatMap((e) => e.statuses.map((e) => e.status)) ?? [])), $ = Q?.points.map((e) => {
192
- let t = { day: j(e.date) };
192
+ })) ?? [], Z = Y ? Math.round(Y.averageVelocity) : 0, Ee = Y ? Math.round(Y.forecastNextSprintPoints) : 0, Q = ve?.planmagnetCumulativeFlow, De = Array.from(new Set(Q?.points.flatMap((e) => e.statuses.map((e) => e.status)) ?? [])), $ = Q?.points.map((e) => {
193
+ let t = { day: N(e.date) };
193
194
  for (let n of e.statuses) t[n.status] = n.count;
194
195
  return t;
195
- }) ?? [], Ee = [
196
+ }) ?? [], Oe = [
196
197
  {
197
- label: M("planmagnet.analytics.metrics.totalWorkItems.label", "Total Work Items"),
198
- value: String(W),
199
- trend: `${Ce}% ${M("planmagnet.analytics.metrics.totalWorkItems.trend", "completed")}`
198
+ label: P("planmagnet.analytics.metrics.totalWorkItems.label", "Total Work Items"),
199
+ value: String(G),
200
+ trend: `${Te}% ${P("planmagnet.analytics.metrics.totalWorkItems.trend", "completed")}`
200
201
  },
201
202
  {
202
- label: M("planmagnet.analytics.metrics.storyPoints.label", "Story Points"),
203
- value: String(be),
204
- trend: `${xe} ${M("planmagnet.analytics.metrics.storyPoints.trend", "completed")}`
203
+ label: P("planmagnet.analytics.metrics.storyPoints.label", "Story Points"),
204
+ value: String(xe),
205
+ trend: `${Se} ${P("planmagnet.analytics.metrics.storyPoints.trend", "completed")}`
205
206
  },
206
207
  {
207
- label: M("planmagnet.analytics.metrics.activeProjects.label", "Active Projects"),
208
+ label: P("planmagnet.analytics.metrics.activeProjects.label", "Active Projects"),
208
209
  value: String(q),
209
- trend: `${L.length} ${M("planmagnet.analytics.metrics.activeProjects.trend", "total")}`
210
+ trend: `${z.length} ${P("planmagnet.analytics.metrics.activeProjects.trend", "total")}`
210
211
  },
211
212
  {
212
- label: M("planmagnet.analytics.metrics.blockedItems.label", "Blocked Items"),
213
- value: String(G),
214
- trend: G > 0 ? M("planmagnet.analytics.metrics.blockedItems.attention", "Needs attention") : M("planmagnet.analytics.metrics.blockedItems.clear", "All clear")
213
+ label: P("planmagnet.analytics.metrics.blockedItems.label", "Blocked Items"),
214
+ value: String(K),
215
+ trend: K > 0 ? P("planmagnet.analytics.metrics.blockedItems.attention", "Needs attention") : P("planmagnet.analytics.metrics.blockedItems.clear", "All clear")
215
216
  }
216
217
  ];
217
- return /* @__PURE__ */ v("div", {
218
+ return /* @__PURE__ */ b("div", {
218
219
  className: "space-y-6 p-6",
219
220
  children: [
220
- /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
221
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h1", {
221
222
  className: "text-2xl font-semibold",
222
- children: M("planmagnet.analytics.title", "Analytics Dashboard")
223
- }), /* @__PURE__ */ _("p", {
223
+ children: P("planmagnet.analytics.title", "Analytics Dashboard")
224
+ }), /* @__PURE__ */ y("p", {
224
225
  className: "text-sm text-muted-foreground",
225
- children: M("planmagnet.analytics.subtitle", "Overview of project health and team performance")
226
+ children: P("planmagnet.analytics.subtitle", "Overview of project health and team performance")
226
227
  })] }),
227
- /* @__PURE__ */ _("div", {
228
+ /* @__PURE__ */ y("div", {
228
229
  className: "grid grid-cols-2 md:grid-cols-4 gap-4",
229
- children: Ee.map((e) => /* @__PURE__ */ _(y, { children: /* @__PURE__ */ v(b, {
230
+ children: Oe.map((e) => /* @__PURE__ */ y(x, { children: /* @__PURE__ */ b(S, {
230
231
  className: "pt-4",
231
232
  children: [
232
- /* @__PURE__ */ _("p", {
233
+ /* @__PURE__ */ y("p", {
233
234
  className: "text-xs text-muted-foreground",
234
235
  children: e.label
235
236
  }),
236
- /* @__PURE__ */ _("p", {
237
+ /* @__PURE__ */ y("p", {
237
238
  className: "text-xl font-bold mt-1",
238
239
  children: e.value
239
240
  }),
240
- /* @__PURE__ */ _("p", {
241
+ /* @__PURE__ */ y("p", {
241
242
  className: "text-xs text-green-600 mt-1",
242
243
  children: e.trend
243
244
  })
244
245
  ]
245
246
  }) }, e.label))
246
247
  }),
247
- /* @__PURE__ */ v("div", {
248
+ /* @__PURE__ */ b("div", {
248
249
  className: "grid grid-cols-2 md:grid-cols-4 gap-4",
249
250
  children: [
250
- /* @__PURE__ */ _(y, { children: /* @__PURE__ */ v(b, {
251
+ /* @__PURE__ */ y(x, { children: /* @__PURE__ */ b(S, {
251
252
  className: "pt-4",
252
- children: [/* @__PURE__ */ v("div", {
253
+ children: [/* @__PURE__ */ b("div", {
253
254
  className: "flex items-center gap-2 text-muted-foreground text-xs",
254
- children: [/* @__PURE__ */ _(re, { className: "h-4 w-4" }), M("planmagnet.analytics.summary.activeProjects", "Active Projects")]
255
- }), /* @__PURE__ */ _("p", {
255
+ children: [/* @__PURE__ */ y(p, { className: "h-4 w-4" }), P("planmagnet.analytics.summary.activeProjects", "Active Projects")]
256
+ }), /* @__PURE__ */ y("p", {
256
257
  className: "text-xl font-bold mt-1",
257
258
  children: q
258
259
  })]
259
260
  }) }),
260
- /* @__PURE__ */ _(y, { children: /* @__PURE__ */ v(b, {
261
+ /* @__PURE__ */ y(x, { children: /* @__PURE__ */ b(S, {
261
262
  className: "pt-4",
262
- children: [/* @__PURE__ */ v("div", {
263
+ children: [/* @__PURE__ */ b("div", {
263
264
  className: "flex items-center gap-2 text-muted-foreground text-xs",
264
- children: [/* @__PURE__ */ _(m, { className: "h-4 w-4" }), M("planmagnet.analytics.summary.activeSprints", "Active Sprints")]
265
- }), /* @__PURE__ */ _("p", {
265
+ children: [/* @__PURE__ */ y(ae, { className: "h-4 w-4" }), P("planmagnet.analytics.summary.activeSprints", "Active Sprints")]
266
+ }), /* @__PURE__ */ y("p", {
266
267
  className: "text-xl font-bold mt-1",
267
- children: K
268
+ children: Ce
268
269
  })]
269
270
  }) }),
270
- /* @__PURE__ */ _(y, { children: /* @__PURE__ */ v(b, {
271
+ /* @__PURE__ */ y(x, { children: /* @__PURE__ */ b(S, {
271
272
  className: "pt-4",
272
- children: [/* @__PURE__ */ v("div", {
273
+ children: [/* @__PURE__ */ b("div", {
273
274
  className: "flex items-center gap-2 text-muted-foreground text-xs",
274
- children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), M("planmagnet.analytics.summary.avgVelocity", "Avg Velocity")]
275
- }), /* @__PURE__ */ v("p", {
275
+ children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), P("planmagnet.analytics.summary.avgVelocity", "Avg Velocity")]
276
+ }), /* @__PURE__ */ b("p", {
276
277
  className: "text-xl font-bold mt-1",
277
278
  children: [
278
279
  Z,
279
280
  " ",
280
- M("planmagnet.analytics.summary.pointsShort", "pts")
281
+ P("planmagnet.analytics.summary.pointsShort", "pts")
281
282
  ]
282
283
  })]
283
284
  }) }),
284
- /* @__PURE__ */ _(y, { children: /* @__PURE__ */ v(b, {
285
+ /* @__PURE__ */ y(x, { children: /* @__PURE__ */ b(S, {
285
286
  className: "pt-4",
286
- children: [/* @__PURE__ */ v("div", {
287
+ children: [/* @__PURE__ */ b("div", {
287
288
  className: "flex items-center gap-2 text-muted-foreground text-xs",
288
- children: [/* @__PURE__ */ _(ie, { className: "h-4 w-4" }), M("planmagnet.analytics.summary.atRisk", "At Risk")]
289
- }), /* @__PURE__ */ v("p", {
289
+ children: [/* @__PURE__ */ y(m, { className: "h-4 w-4" }), P("planmagnet.analytics.summary.atRisk", "At Risk")]
290
+ }), /* @__PURE__ */ b("p", {
290
291
  className: "text-xl font-bold mt-1",
291
292
  children: [
292
- Se,
293
+ we,
293
294
  " ",
294
- M("planmagnet.analytics.summary.milestones", "milestones")
295
+ P("planmagnet.analytics.summary.milestones", "milestones")
295
296
  ]
296
297
  })]
297
298
  }) })
298
299
  ]
299
300
  }),
300
- /* @__PURE__ */ v("div", {
301
+ /* @__PURE__ */ b("div", {
301
302
  className: "flex flex-col sm:flex-row gap-3 items-start sm:items-center",
302
- children: [/* @__PURE__ */ _("h2", {
303
+ children: [/* @__PURE__ */ y("h2", {
303
304
  className: "text-lg font-semibold",
304
- children: M("planmagnet.analytics.delivery.title", "Sprint Delivery Analytics")
305
- }), /* @__PURE__ */ v("div", {
305
+ children: P("planmagnet.analytics.delivery.title", "Sprint Delivery Analytics")
306
+ }), /* @__PURE__ */ b("div", {
306
307
  className: "flex gap-2 sm:ml-auto",
307
- children: [/* @__PURE__ */ v(C, {
308
- value: V,
308
+ children: [/* @__PURE__ */ b(T, {
309
+ value: H,
309
310
  onValueChange: (e) => {
310
- de(e), N("");
311
+ de(e), F("");
311
312
  },
312
- children: [/* @__PURE__ */ _(E, {
313
+ children: [/* @__PURE__ */ y(O, {
313
314
  className: "h-8 w-48",
314
- children: /* @__PURE__ */ _(D, { placeholder: M("planmagnet.analytics.delivery.projectPlaceholder", "Select project") })
315
- }), /* @__PURE__ */ _(w, { children: L.map((e) => /* @__PURE__ */ _(T, {
315
+ children: /* @__PURE__ */ y(k, { placeholder: P("planmagnet.analytics.delivery.projectPlaceholder", "Select project") })
316
+ }), /* @__PURE__ */ y(E, { children: z.map((e) => /* @__PURE__ */ y(D, {
316
317
  value: e.id,
317
318
  children: e.name
318
319
  }, e.id)) })]
319
- }), /* @__PURE__ */ v(C, {
320
- value: U,
321
- onValueChange: N,
322
- children: [/* @__PURE__ */ _(E, {
320
+ }), /* @__PURE__ */ b(T, {
321
+ value: W,
322
+ onValueChange: F,
323
+ children: [/* @__PURE__ */ y(O, {
323
324
  className: "h-8 w-48",
324
- children: /* @__PURE__ */ _(D, { placeholder: M("planmagnet.analytics.delivery.sprintPlaceholder", "Select sprint") })
325
- }), /* @__PURE__ */ _(w, { children: H.map((e) => /* @__PURE__ */ _(T, {
325
+ children: /* @__PURE__ */ y(k, { placeholder: P("planmagnet.analytics.delivery.sprintPlaceholder", "Select sprint") })
326
+ }), /* @__PURE__ */ y(E, { children: U.map((e) => /* @__PURE__ */ y(D, {
326
327
  value: e.id,
327
328
  children: e.name
328
329
  }, e.id)) })]
329
330
  })]
330
331
  })]
331
332
  }),
332
- /* @__PURE__ */ v("div", {
333
+ /* @__PURE__ */ b("div", {
333
334
  className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
334
- children: [/* @__PURE__ */ v(y, { children: [/* @__PURE__ */ _(x, { children: /* @__PURE__ */ v(S, {
335
+ children: [/* @__PURE__ */ b(x, { children: [/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
335
336
  className: "flex items-center gap-2",
336
- children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), M("planmagnet.analytics.burndown.title", "Sprint Burndown")]
337
- }) }), /* @__PURE__ */ _(b, { children: me ? /* @__PURE__ */ _("div", {
337
+ children: [/* @__PURE__ */ y(f, { className: "h-4 w-4" }), P("planmagnet.analytics.burndown.title", "Sprint Burndown")]
338
+ }) }), /* @__PURE__ */ y(S, { children: he ? /* @__PURE__ */ y("div", {
338
339
  className: "flex justify-center py-12",
339
- children: /* @__PURE__ */ _("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
340
- }) : J.length > 0 ? /* @__PURE__ */ _(r, {
340
+ children: /* @__PURE__ */ y("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
341
+ }) : J.length > 0 ? /* @__PURE__ */ y(r, {
341
342
  width: "100%",
342
343
  height: 220,
343
- children: /* @__PURE__ */ v(te, {
344
+ children: /* @__PURE__ */ b(ne, {
344
345
  data: J,
345
346
  children: [
346
- /* @__PURE__ */ _(a, {
347
+ /* @__PURE__ */ y(a, {
347
348
  strokeDasharray: "3 3",
348
349
  opacity: .2
349
350
  }),
350
- /* @__PURE__ */ _(c, {
351
+ /* @__PURE__ */ y(s, {
351
352
  dataKey: "day",
352
353
  tick: { fontSize: 11 }
353
354
  }),
354
- /* @__PURE__ */ _(l, {
355
+ /* @__PURE__ */ y(c, {
355
356
  tick: { fontSize: 11 },
356
357
  allowDecimals: !1
357
358
  }),
358
- /* @__PURE__ */ _(n, {}),
359
- /* @__PURE__ */ _(t, { wrapperStyle: { fontSize: 12 } }),
360
- /* @__PURE__ */ _(o, {
359
+ /* @__PURE__ */ y(n, {}),
360
+ /* @__PURE__ */ y(t, { wrapperStyle: { fontSize: 12 } }),
361
+ /* @__PURE__ */ y(o, {
361
362
  type: "monotone",
362
363
  dataKey: "remaining",
363
- name: M("planmagnet.analytics.burndown.remaining", "Remaining points"),
364
+ name: P("planmagnet.analytics.burndown.remaining", "Remaining points"),
364
365
  stroke: "#f59e0b",
365
366
  strokeWidth: 2,
366
367
  dot: { r: 2 }
367
368
  }),
368
- /* @__PURE__ */ _(o, {
369
+ /* @__PURE__ */ y(o, {
369
370
  type: "monotone",
370
371
  dataKey: "completed",
371
- name: M("planmagnet.analytics.burndown.completed", "Completed points"),
372
+ name: P("planmagnet.analytics.burndown.completed", "Completed points"),
372
373
  stroke: "#22c55e",
373
374
  strokeWidth: 2,
374
375
  dot: { r: 2 }
375
376
  })
376
377
  ]
377
378
  })
378
- }) : /* @__PURE__ */ _("p", {
379
+ }) : /* @__PURE__ */ y("p", {
379
380
  className: "text-sm text-muted-foreground py-8 text-center",
380
- children: M("planmagnet.analytics.burndown.empty", "No sprint selected, or the sprint has no work items yet")
381
- }) })] }), /* @__PURE__ */ v(y, { children: [/* @__PURE__ */ _(x, { children: /* @__PURE__ */ v(S, {
381
+ children: P("planmagnet.analytics.burndown.empty", "No sprint selected, or the sprint has no work items yet")
382
+ }) })] }), /* @__PURE__ */ b(x, { children: [/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
382
383
  className: "flex items-center gap-2",
383
384
  children: [
384
- /* @__PURE__ */ _(p, { className: "h-4 w-4" }),
385
- M("planmagnet.analytics.velocity.title", "Sprint Velocity"),
385
+ /* @__PURE__ */ y(h, { className: "h-4 w-4" }),
386
+ P("planmagnet.analytics.velocity.title", "Sprint Velocity"),
386
387
  " (",
387
388
  X.length,
388
389
  " ",
389
- M("planmagnet.analytics.velocity.sprints", "Sprints"),
390
+ P("planmagnet.analytics.velocity.sprints", "Sprints"),
390
391
  ")"
391
392
  ]
392
- }) }), /* @__PURE__ */ _(b, { children: ge ? /* @__PURE__ */ _("div", {
393
+ }) }), /* @__PURE__ */ y(S, { children: _e ? /* @__PURE__ */ y("div", {
393
394
  className: "flex justify-center py-12",
394
- children: /* @__PURE__ */ _("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
395
- }) : X.length > 0 ? /* @__PURE__ */ v(ae, { children: [/* @__PURE__ */ _(r, {
395
+ children: /* @__PURE__ */ y("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
396
+ }) : X.length > 0 ? /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(r, {
396
397
  width: "100%",
397
398
  height: 190,
398
- children: /* @__PURE__ */ v(ne, {
399
+ children: /* @__PURE__ */ b(re, {
399
400
  data: X,
400
401
  children: [
401
- /* @__PURE__ */ _(a, {
402
+ /* @__PURE__ */ y(a, {
402
403
  strokeDasharray: "3 3",
403
404
  opacity: .2
404
405
  }),
405
- /* @__PURE__ */ _(c, {
406
+ /* @__PURE__ */ y(s, {
406
407
  dataKey: "name",
407
408
  tick: { fontSize: 11 }
408
409
  }),
409
- /* @__PURE__ */ _(l, {
410
+ /* @__PURE__ */ y(c, {
410
411
  tick: { fontSize: 11 },
411
412
  allowDecimals: !1
412
413
  }),
413
- /* @__PURE__ */ _(n, {}),
414
- /* @__PURE__ */ _(t, { wrapperStyle: { fontSize: 12 } }),
415
- /* @__PURE__ */ _(i, {
414
+ /* @__PURE__ */ y(n, {}),
415
+ /* @__PURE__ */ y(t, { wrapperStyle: { fontSize: 12 } }),
416
+ /* @__PURE__ */ y(i, {
416
417
  dataKey: "planned",
417
- name: M("planmagnet.analytics.velocity.planned", "Planned"),
418
+ name: P("planmagnet.analytics.velocity.planned", "Planned"),
418
419
  fill: "#cbd5e1"
419
420
  }),
420
- /* @__PURE__ */ _(i, {
421
+ /* @__PURE__ */ y(i, {
421
422
  dataKey: "completed",
422
- name: M("planmagnet.analytics.velocity.completed", "Completed"),
423
+ name: P("planmagnet.analytics.velocity.completed", "Completed"),
423
424
  fill: "#6366f1"
424
425
  }),
425
- /* @__PURE__ */ _(ee, {
426
+ /* @__PURE__ */ y(ee, {
426
427
  y: Z,
427
428
  stroke: "#22c55e",
428
429
  strokeDasharray: "4 4",
429
430
  label: {
430
- value: M("planmagnet.analytics.velocity.avgLabel", "avg"),
431
+ value: P("planmagnet.analytics.velocity.avgLabel", "avg"),
431
432
  fontSize: 10,
432
433
  position: "right"
433
434
  }
434
435
  })
435
436
  ]
436
437
  })
437
- }), /* @__PURE__ */ _("p", {
438
+ }), /* @__PURE__ */ y("p", {
438
439
  className: "text-xs text-muted-foreground text-center mt-2",
439
- children: M("planmagnet.analytics.velocity.forecast", "Forecast for next sprint: {{points}} pts (trailing average)", { points: we })
440
- })] }) : /* @__PURE__ */ _("p", {
440
+ children: P("planmagnet.analytics.velocity.forecast", "Forecast for next sprint: {{points}} pts (trailing average)", { points: Ee })
441
+ })] }) : /* @__PURE__ */ y("p", {
441
442
  className: "text-sm text-muted-foreground py-8 text-center",
442
- children: M("planmagnet.analytics.velocity.empty", "No completed sprints yet for this project")
443
+ children: P("planmagnet.analytics.velocity.empty", "No completed sprints yet for this project")
443
444
  }) })] })]
444
445
  }),
445
- /* @__PURE__ */ v(y, { children: [/* @__PURE__ */ _(x, { children: /* @__PURE__ */ v(S, {
446
+ /* @__PURE__ */ b(x, { children: [/* @__PURE__ */ y(C, { children: /* @__PURE__ */ b(w, {
446
447
  className: "flex items-center gap-2",
447
- children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), M("planmagnet.analytics.cfd.title", "Cumulative Flow Diagram")]
448
- }) }), /* @__PURE__ */ _(b, { children: ve ? /* @__PURE__ */ _("div", {
448
+ children: [/* @__PURE__ */ y(f, { className: "h-4 w-4" }), P("planmagnet.analytics.cfd.title", "Cumulative Flow Diagram")]
449
+ }) }), /* @__PURE__ */ y(S, { children: ye ? /* @__PURE__ */ y("div", {
449
450
  className: "flex justify-center py-12",
450
- children: /* @__PURE__ */ _("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
451
- }) : $.length > 0 ? /* @__PURE__ */ _(r, {
451
+ children: /* @__PURE__ */ y("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
452
+ }) : $.length > 0 ? /* @__PURE__ */ y(r, {
452
453
  width: "100%",
453
454
  height: 260,
454
- children: /* @__PURE__ */ v(u, {
455
+ children: /* @__PURE__ */ b(ie, {
455
456
  data: $,
456
457
  children: [
457
- /* @__PURE__ */ _(a, {
458
+ /* @__PURE__ */ y(a, {
458
459
  strokeDasharray: "3 3",
459
460
  opacity: .2
460
461
  }),
461
- /* @__PURE__ */ _(c, {
462
+ /* @__PURE__ */ y(s, {
462
463
  dataKey: "day",
463
464
  tick: { fontSize: 11 }
464
465
  }),
465
- /* @__PURE__ */ _(l, {
466
+ /* @__PURE__ */ y(c, {
466
467
  tick: { fontSize: 11 },
467
468
  allowDecimals: !1
468
469
  }),
469
- /* @__PURE__ */ _(n, {}),
470
- /* @__PURE__ */ _(t, { wrapperStyle: { fontSize: 12 } }),
471
- Te.map((e) => /* @__PURE__ */ _(s, {
470
+ /* @__PURE__ */ y(n, {}),
471
+ /* @__PURE__ */ y(t, { wrapperStyle: { fontSize: 12 } }),
472
+ De.map((e) => /* @__PURE__ */ y(te, {
472
473
  type: "monotone",
473
474
  dataKey: e,
474
475
  name: e.toLowerCase().replace("_", " "),
475
476
  stackId: "cfd",
476
- stroke: A[e] ?? "#a3a3a3",
477
- fill: A[e] ?? "#a3a3a3",
477
+ stroke: M[e] ?? "#a3a3a3",
478
+ fill: M[e] ?? "#a3a3a3",
478
479
  fillOpacity: .7
479
480
  }, e))
480
481
  ]
481
482
  })
482
- }) : /* @__PURE__ */ _("p", {
483
+ }) : /* @__PURE__ */ y("p", {
483
484
  className: "text-sm text-muted-foreground py-8 text-center",
484
- children: M("planmagnet.analytics.cfd.empty", "No work items found for this project")
485
+ children: P("planmagnet.analytics.cfd.empty", "No work items found for this project")
485
486
  }) })] }),
486
- /* @__PURE__ */ v(y, { children: [/* @__PURE__ */ _(x, { children: /* @__PURE__ */ _(S, { children: M("planmagnet.analytics.projectHealth.title", "Project Health") }) }), /* @__PURE__ */ _(b, { children: /* @__PURE__ */ v("div", {
487
+ /* @__PURE__ */ b("div", {
488
+ className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
489
+ children: [/* @__PURE__ */ y(u, {
490
+ workspaceId: g,
491
+ projectId: H
492
+ }), /* @__PURE__ */ y(l, { workspaceId: g })]
493
+ }),
494
+ /* @__PURE__ */ b(x, { children: [/* @__PURE__ */ y(C, { children: /* @__PURE__ */ y(w, { children: P("planmagnet.analytics.projectHealth.title", "Project Health") }) }), /* @__PURE__ */ y(S, { children: /* @__PURE__ */ b("div", {
487
495
  className: "space-y-3",
488
- children: [L.filter((e) => e.status === "ACTIVE").map((e) => {
489
- let t = R.filter((e) => e.assigneeId != null), n = t.filter((e) => e.status === "DONE").length, r = t.length > 0 ? Math.round(n / t.length * 100) : 0, i = e.endDate && new Date(e.endDate) < /* @__PURE__ */ new Date() && r < 100 ? "at_risk" : r >= 75 ? "on_track" : "in_progress";
490
- return /* @__PURE__ */ v("div", {
496
+ children: [z.filter((e) => e.status === "ACTIVE").map((e) => {
497
+ let t = B.filter((e) => e.assigneeId != null), n = t.filter((e) => e.status === "DONE").length, r = t.length > 0 ? Math.round(n / t.length * 100) : 0, i = e.endDate && new Date(e.endDate) < /* @__PURE__ */ new Date() && r < 100 ? "at_risk" : r >= 75 ? "on_track" : "in_progress";
498
+ return /* @__PURE__ */ b("div", {
491
499
  className: "flex items-center gap-4",
492
500
  children: [
493
- /* @__PURE__ */ _("span", {
501
+ /* @__PURE__ */ y("span", {
494
502
  className: "text-sm font-medium w-40 truncate",
495
503
  children: e.name
496
504
  }),
497
- /* @__PURE__ */ _("div", {
505
+ /* @__PURE__ */ y("div", {
498
506
  className: "flex-1",
499
- children: /* @__PURE__ */ _("div", {
507
+ children: /* @__PURE__ */ y("div", {
500
508
  className: "w-full h-2 rounded-full bg-muted overflow-hidden",
501
- children: /* @__PURE__ */ _("div", {
509
+ children: /* @__PURE__ */ y("div", {
502
510
  className: `h-full rounded-full ${i === "at_risk" ? "bg-orange-400" : i === "on_track" ? "bg-green-400" : "bg-blue-400"}`,
503
511
  style: { width: `${r}%` }
504
512
  })
505
513
  })
506
514
  }),
507
- /* @__PURE__ */ v("span", {
515
+ /* @__PURE__ */ b("span", {
508
516
  className: "text-sm w-12 text-right",
509
517
  children: [r, "%"]
510
518
  }),
511
- /* @__PURE__ */ _(se, {
519
+ /* @__PURE__ */ y(se, {
512
520
  variant: "outline",
513
521
  className: "capitalize text-xs w-20 justify-center",
514
522
  children: i.replace("_", " ")
515
523
  })
516
524
  ]
517
525
  }, e.id);
518
- }), L.filter((e) => e.status === "ACTIVE").length === 0 && /* @__PURE__ */ _("p", {
526
+ }), z.filter((e) => e.status === "ACTIVE").length === 0 && /* @__PURE__ */ y("p", {
519
527
  className: "text-sm text-muted-foreground text-center py-4",
520
- children: M("planmagnet.analytics.projectHealth.empty", "No active projects found")
528
+ children: P("planmagnet.analytics.projectHealth.empty", "No active projects found")
521
529
  })]
522
530
  }) })] })
523
531
  ]
524
532
  });
525
533
  }
526
534
  //#endregion
527
- export { M as AnalyticsAnalyticsPagePage };
535
+ export { P as AnalyticsAnalyticsPagePage };
528
536
 
529
537
  //# sourceMappingURL=AnalyticsAnalyticsPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\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';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n $sprintPagination: PlanmagnetPaginationInput\n $milestonePagination: PlanmagnetPaginationInput\n ) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n priority\n storyPoints\n assigneeId\n }\n totalCount\n }\n planmagnetSprints(workspaceId: $workspaceId, pagination: $sprintPagination) {\n items {\n id\n name\n status\n velocity\n capacity\n projectId\n startDate\n endDate\n }\n totalCount\n }\n planmagnetMilestones(workspaceId: $workspaceId, pagination: $milestonePagination) {\n items {\n id\n name\n status\n dueDate\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($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 completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow($workspaceId: ID!, $projectId: ID!) {\n planmagnetCumulativeFlow(workspaceId: $workspaceId, projectId: $projectId) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface WorkItemNode {\n id: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n}\n\ninterface SprintNode {\n id: string;\n name: string;\n status: string;\n velocity: number | null;\n capacity: number | null;\n projectId: string;\n startDate: string | null;\n endDate: string | null;\n}\n\ninterface MilestoneNode {\n id: string;\n name: string;\n status: string;\n dueDate: string | null;\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n planmagnetWorkItems: { items: WorkItemNode[]; totalCount: number };\n planmagnetSprints: { items: SprintNode[]; totalCount: number };\n planmagnetMilestones: { items: MilestoneNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\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 completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedProjectId, setSelectedProjectId] = useState<string>('');\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n sprintPagination: { first: 100 },\n milestonePagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n const tasks = data?.planmagnetWorkItems?.items ?? [];\n const sprints = data?.planmagnetSprints?.items ?? [];\n const milestones = data?.planmagnetMilestones?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n const effectiveProjectId = selectedProjectId || projects[0]?.id || '';\n const sprintsForProject = sprints.filter((s) => s.projectId === effectiveProjectId);\n const effectiveSprintId = selectedSprintId || sprintsForProject[0]?.id || '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\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 }\n\n if (error) {\n return (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n const totalTasks = tasks.length;\n const doneTasks = tasks.filter((t) => t.status === 'DONE').length;\n const blockedTasks = tasks.filter((t) => t.status === 'BLOCKED').length;\n const totalPoints = tasks.reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n const completedPoints = tasks\n .filter((t) => t.status === 'DONE')\n .reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n\n const activeSprints = sprints.filter((s) => s.status === 'ACTIVE').length;\n const atRiskMs = milestones.filter(\n (m) => m.status === 'AT_RISK' || m.status === 'OVERDUE'\n ).length;\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const completionRate = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\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\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: String(totalTasks),\n trend: `${completionRate}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: String(totalPoints),\n trend: `${completedPoints} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: String(activeProjects),\n trend: `${projects.length} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: String(blockedTasks),\n trend:\n blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{activeProjects}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{activeSprints}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {atRiskMs} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select\n value={effectiveProjectId}\n onValueChange={(v) => {\n setSelectedProjectId(v);\n setSelectedSprintId('');\n }}\n >\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t(\n 'planmagnet.analytics.delivery.projectPlaceholder',\n 'Select project'\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\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.analytics.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.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\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.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.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.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {t(\n 'planmagnet.analytics.velocity.forecast',\n 'Forecast for next sprint: {{points}} pts (trailing average)',\n { points: forecastNextSprint }\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\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 {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n const projectTasks = tasks.filter((t) => t.assigneeId != null);\n const projectDone = projectTasks.filter((t) => t.status === 'DONE').length;\n const progress =\n projectTasks.length > 0\n ? Math.round((projectDone / projectTasks.length) * 100)\n : 0;\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\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';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\n\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n $sprintPagination: PlanmagnetPaginationInput\n $milestonePagination: PlanmagnetPaginationInput\n ) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n priority\n storyPoints\n assigneeId\n }\n totalCount\n }\n planmagnetSprints(workspaceId: $workspaceId, pagination: $sprintPagination) {\n items {\n id\n name\n status\n velocity\n capacity\n projectId\n startDate\n endDate\n }\n totalCount\n }\n planmagnetMilestones(workspaceId: $workspaceId, pagination: $milestonePagination) {\n items {\n id\n name\n status\n dueDate\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($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 completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow($workspaceId: ID!, $projectId: ID!) {\n planmagnetCumulativeFlow(workspaceId: $workspaceId, projectId: $projectId) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface WorkItemNode {\n id: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n}\n\ninterface SprintNode {\n id: string;\n name: string;\n status: string;\n velocity: number | null;\n capacity: number | null;\n projectId: string;\n startDate: string | null;\n endDate: string | null;\n}\n\ninterface MilestoneNode {\n id: string;\n name: string;\n status: string;\n dueDate: string | null;\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n planmagnetWorkItems: { items: WorkItemNode[]; totalCount: number };\n planmagnetSprints: { items: SprintNode[]; totalCount: number };\n planmagnetMilestones: { items: MilestoneNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\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 completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedProjectId, setSelectedProjectId] = useState<string>('');\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n sprintPagination: { first: 100 },\n milestonePagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n const tasks = data?.planmagnetWorkItems?.items ?? [];\n const sprints = data?.planmagnetSprints?.items ?? [];\n const milestones = data?.planmagnetMilestones?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n const effectiveProjectId = selectedProjectId || projects[0]?.id || '';\n const sprintsForProject = sprints.filter((s) => s.projectId === effectiveProjectId);\n const effectiveSprintId = selectedSprintId || sprintsForProject[0]?.id || '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\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 }\n\n if (error) {\n return (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n const totalTasks = tasks.length;\n const doneTasks = tasks.filter((t) => t.status === 'DONE').length;\n const blockedTasks = tasks.filter((t) => t.status === 'BLOCKED').length;\n const totalPoints = tasks.reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n const completedPoints = tasks\n .filter((t) => t.status === 'DONE')\n .reduce((s, t) => s + (t.storyPoints ?? 0), 0);\n\n const activeSprints = sprints.filter((s) => s.status === 'ACTIVE').length;\n const atRiskMs = milestones.filter(\n (m) => m.status === 'AT_RISK' || m.status === 'OVERDUE'\n ).length;\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const completionRate = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\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\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: String(totalTasks),\n trend: `${completionRate}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: String(totalPoints),\n trend: `${completedPoints} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: String(activeProjects),\n trend: `${projects.length} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: String(blockedTasks),\n trend:\n blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{activeProjects}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{activeSprints}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {atRiskMs} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select\n value={effectiveProjectId}\n onValueChange={(v) => {\n setSelectedProjectId(v);\n setSelectedSprintId('');\n }}\n >\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t(\n 'planmagnet.analytics.delivery.projectPlaceholder',\n 'Select project'\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\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.analytics.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.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\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.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.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.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {t(\n 'planmagnet.analytics.velocity.forecast',\n 'Forecast for next sprint: {{points}} pts (trailing average)',\n { points: forecastNextSprint }\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\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 {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n {/* Cycle time follows the delivery selector above; budgets are workspace-wide. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard workspaceId={workspaceId} projectId={effectiveProjectId} />\n <BudgetPerformanceCard workspaceId={workspaceId} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n const projectTasks = tasks.filter((t) => t.assigneeId != null);\n const projectDone = projectTasks.filter((t) => t.status === 'DONE').length;\n const progress =\n projectTasks.length > 0\n ? Math.round((projectDone / projectTasks.length) * 100)\n : 0;\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** Hours read naturally up to two days; beyond that, days. */
2
+ export declare function formatDuration(hours: number | null | undefined): string;
3
+ export declare function CycleTimeCard({ workspaceId, projectId, }: {
4
+ workspaceId?: string | null;
5
+ projectId: string;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ type BudgetState = 'no-plan' | 'on-track' | 'near-limit' | 'over-budget';
8
+ /** Share of the plan spent; `null` when there is no plan to measure against. */
9
+ export declare function budgetUsage(planned: number, spent: number): number | null;
10
+ export declare function budgetState(usage: number | null): BudgetState;
11
+ export declare function BudgetPerformanceCard({ workspaceId }: {
12
+ workspaceId?: string | null;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=DeliveryHealthCards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeliveryHealthCards.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.tsx"],"names":[],"mappings":"AAyFA,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKvE;AAYD,wBAAgB,aAAa,CAAC,EAC5B,WAAW,EACX,SAAS,GACV,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,2CAkGA;AAED,KAAK,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;AAEzE,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAM7D;AA6CD,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,2CA8GrF"}
@@ -0,0 +1,302 @@
1
+ import { AlertOctagon as e, AlertTriangle as t, CheckCircle2 as n, CircleDashed as r, Timer as i, Wallet as a } from "lucide-react";
2
+ import { gql as o } from "@apollo/client";
3
+ import { useQuery as s } from "@apollo/client/react";
4
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
+ import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { Card as d, CardContent as f, CardHeader as p, CardTitle as m } from "@burdenoff/fe-libs/ui";
7
+ //#region src/planmagnet/pages/analytics/DeliveryHealthCards.tsx
8
+ var h = o`
9
+ query GetProjectCycleTime($workspaceId: ID!, $projectId: ID!) {
10
+ planmagnetCycleTime(workspaceId: $workspaceId, projectId: $projectId) {
11
+ averageCycleTimeHours
12
+ medianCycleTimeHours
13
+ sampleSize
14
+ items {
15
+ workItemId
16
+ key
17
+ title
18
+ cycleTimeHours
19
+ }
20
+ }
21
+ }
22
+ `, g = o`
23
+ query GetBudgetPerformance($workspaceId: ID!) {
24
+ planmagnetBudgets(workspaceId: $workspaceId, pagination: { first: 50 }) {
25
+ items {
26
+ id
27
+ name
28
+ currency
29
+ plannedAmount
30
+ spentAmount
31
+ remainingAmount
32
+ project {
33
+ id
34
+ name
35
+ }
36
+ }
37
+ }
38
+ }
39
+ `;
40
+ function _(e) {
41
+ if (e == null || !Number.isFinite(e)) return "—";
42
+ if (e < 48) return `${e < 10 ? e.toFixed(1) : Math.round(e)} h`;
43
+ let t = e / 24;
44
+ return `${t < 10 ? t.toFixed(1) : Math.round(t)} d`;
45
+ }
46
+ function v({ label: e, value: t, hint: n }) {
47
+ return /* @__PURE__ */ l("div", {
48
+ className: "rounded-md border p-3",
49
+ children: [
50
+ /* @__PURE__ */ c("p", {
51
+ className: "text-xs text-muted-foreground",
52
+ children: e
53
+ }),
54
+ /* @__PURE__ */ c("p", {
55
+ className: "mt-1 text-2xl font-semibold",
56
+ children: t
57
+ }),
58
+ n ? /* @__PURE__ */ c("p", {
59
+ className: "mt-0.5 text-xs text-muted-foreground",
60
+ children: n
61
+ }) : null
62
+ ]
63
+ });
64
+ }
65
+ function y({ workspaceId: e, projectId: t }) {
66
+ let { t: n } = u(), { data: r, loading: a, error: o } = s(h, {
67
+ variables: {
68
+ workspaceId: e,
69
+ projectId: t
70
+ },
71
+ skip: !e || !t
72
+ }), g = r?.planmagnetCycleTime, y = [...g?.items ?? []].sort((e, t) => t.cycleTimeHours - e.cycleTimeHours).slice(0, 5);
73
+ return /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ l(p, { children: [/* @__PURE__ */ l(m, {
74
+ className: "flex items-center gap-2",
75
+ children: [/* @__PURE__ */ c(i, { className: "h-4 w-4" }), n("planmagnet.analytics.cycleTime.title", "Cycle time")]
76
+ }), /* @__PURE__ */ c("p", {
77
+ className: "text-xs text-muted-foreground",
78
+ children: n("planmagnet.analytics.cycleTime.subtitle", "From first moving to In Progress (or creation) to Done, for the selected project.")
79
+ })] }), /* @__PURE__ */ c(f, { children: t ? a ? /* @__PURE__ */ c("p", {
80
+ className: "text-sm text-muted-foreground",
81
+ children: n("planmagnet.analytics.loading", "Loading…")
82
+ }) : o ? /* @__PURE__ */ l("p", {
83
+ className: "text-sm text-status-error-text",
84
+ children: [
85
+ n("planmagnet.analytics.cycleTime.error", "Cycle time could not be loaded"),
86
+ ":",
87
+ " ",
88
+ o.message
89
+ ]
90
+ }) : !g || g.sampleSize === 0 ? /* @__PURE__ */ c("p", {
91
+ className: "text-sm text-muted-foreground",
92
+ children: n("planmagnet.analytics.cycleTime.empty", "No completed work items in this project yet — cycle time appears once items reach Done.")
93
+ }) : /* @__PURE__ */ l("div", {
94
+ className: "space-y-4",
95
+ children: [/* @__PURE__ */ l("div", {
96
+ className: "grid grid-cols-3 gap-3",
97
+ children: [
98
+ /* @__PURE__ */ c(v, {
99
+ label: n("planmagnet.analytics.cycleTime.median", "Median"),
100
+ value: _(g.medianCycleTimeHours)
101
+ }),
102
+ /* @__PURE__ */ c(v, {
103
+ label: n("planmagnet.analytics.cycleTime.average", "Average"),
104
+ value: _(g.averageCycleTimeHours)
105
+ }),
106
+ /* @__PURE__ */ c(v, {
107
+ label: n("planmagnet.analytics.cycleTime.sample", "Items measured"),
108
+ value: String(g.sampleSize)
109
+ })
110
+ ]
111
+ }), /* @__PURE__ */ l("table", {
112
+ className: "w-full text-sm",
113
+ children: [
114
+ /* @__PURE__ */ c("caption", {
115
+ className: "mb-2 text-left text-xs text-muted-foreground",
116
+ children: n("planmagnet.analytics.cycleTime.slowest", "Slowest completed items")
117
+ }),
118
+ /* @__PURE__ */ c("thead", { children: /* @__PURE__ */ l("tr", {
119
+ className: "text-left text-xs text-muted-foreground",
120
+ children: [/* @__PURE__ */ c("th", {
121
+ className: "py-1 pr-2 font-normal",
122
+ children: n("planmagnet.analytics.cycleTime.item", "Item")
123
+ }), /* @__PURE__ */ c("th", {
124
+ className: "py-1 text-right font-normal",
125
+ children: n("planmagnet.analytics.cycleTime.duration", "Cycle time")
126
+ })]
127
+ }) }),
128
+ /* @__PURE__ */ c("tbody", { children: y.map((e) => /* @__PURE__ */ l("tr", {
129
+ className: "border-t",
130
+ children: [/* @__PURE__ */ l("td", {
131
+ className: "py-1.5 pr-2",
132
+ children: [
133
+ /* @__PURE__ */ c("span", {
134
+ className: "font-medium",
135
+ children: e.key
136
+ }),
137
+ " ",
138
+ /* @__PURE__ */ c("span", {
139
+ className: "text-muted-foreground",
140
+ children: e.title
141
+ })
142
+ ]
143
+ }), /* @__PURE__ */ c("td", {
144
+ className: "py-1.5 text-right tabular-nums",
145
+ children: _(e.cycleTimeHours)
146
+ })]
147
+ }, e.workItemId)) })
148
+ ]
149
+ })]
150
+ }) : /* @__PURE__ */ c("p", {
151
+ className: "text-sm text-muted-foreground",
152
+ children: n("planmagnet.analytics.cycleTime.noProject", "Select a project to see its cycle time.")
153
+ }) })] });
154
+ }
155
+ function b(e, t) {
156
+ return !Number.isFinite(e) || e <= 0 ? null : t / e;
157
+ }
158
+ function x(e) {
159
+ return e === null ? "no-plan" : e <= .85 ? "on-track" : e <= 1 ? "near-limit" : "over-budget";
160
+ }
161
+ var S = {
162
+ "no-plan": {
163
+ track: "bg-muted",
164
+ fill: "bg-muted",
165
+ Icon: r,
166
+ text: "text-muted-foreground"
167
+ },
168
+ "on-track": {
169
+ track: "bg-primary/15",
170
+ fill: "bg-primary",
171
+ Icon: n,
172
+ text: "text-muted-foreground"
173
+ },
174
+ "near-limit": {
175
+ track: "bg-status-warning-subtle",
176
+ fill: "bg-status-warning",
177
+ Icon: t,
178
+ text: "text-status-warning-text"
179
+ },
180
+ "over-budget": {
181
+ track: "bg-status-error-subtle",
182
+ fill: "bg-status-error",
183
+ Icon: e,
184
+ text: "text-status-error-text"
185
+ }
186
+ };
187
+ function C(e, t) {
188
+ try {
189
+ return new Intl.NumberFormat(void 0, {
190
+ style: "currency",
191
+ currency: t,
192
+ maximumFractionDigits: 0
193
+ }).format(e);
194
+ } catch {
195
+ return `${Math.round(e).toLocaleString()} ${t}`;
196
+ }
197
+ }
198
+ function w({ workspaceId: e }) {
199
+ let { t } = u(), { data: n, loading: r, error: i } = s(g, {
200
+ variables: { workspaceId: e },
201
+ skip: !e
202
+ }), o = n?.planmagnetBudgets?.items ?? [], h = {
203
+ "no-plan": t("planmagnet.analytics.budget.noPlan", "No plan set"),
204
+ "on-track": t("planmagnet.analytics.budget.onTrack", "On track"),
205
+ "near-limit": t("planmagnet.analytics.budget.nearLimit", "Near limit"),
206
+ "over-budget": t("planmagnet.analytics.budget.over", "Over budget")
207
+ };
208
+ return /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ l(p, { children: [/* @__PURE__ */ l(m, {
209
+ className: "flex items-center gap-2",
210
+ children: [/* @__PURE__ */ c(a, { className: "h-4 w-4" }), t("planmagnet.analytics.budget.title", "Budget performance")]
211
+ }), /* @__PURE__ */ c("p", {
212
+ className: "text-xs text-muted-foreground",
213
+ children: t("planmagnet.analytics.budget.subtitle", "Spent against plan, per budget, across the workspace.")
214
+ })] }), /* @__PURE__ */ c(f, { children: r ? /* @__PURE__ */ c("p", {
215
+ className: "text-sm text-muted-foreground",
216
+ children: t("planmagnet.analytics.loading", "Loading…")
217
+ }) : i ? /* @__PURE__ */ l("p", {
218
+ className: "text-sm text-status-error-text",
219
+ children: [
220
+ t("planmagnet.analytics.budget.error", "Budgets could not be loaded"),
221
+ ": ",
222
+ i.message
223
+ ]
224
+ }) : o.length === 0 ? /* @__PURE__ */ c("p", {
225
+ className: "text-sm text-muted-foreground",
226
+ children: t("planmagnet.analytics.budget.empty", "No budgets in this workspace yet.")
227
+ }) : /* @__PURE__ */ l("table", {
228
+ className: "w-full text-sm",
229
+ children: [/* @__PURE__ */ c("thead", { children: /* @__PURE__ */ l("tr", {
230
+ className: "text-left text-xs text-muted-foreground",
231
+ children: [
232
+ /* @__PURE__ */ c("th", {
233
+ className: "py-1 pr-2 font-normal",
234
+ children: t("planmagnet.analytics.budget.name", "Budget")
235
+ }),
236
+ /* @__PURE__ */ c("th", {
237
+ className: "py-1 pr-2 text-right font-normal",
238
+ children: t("planmagnet.analytics.budget.spent", "Spent")
239
+ }),
240
+ /* @__PURE__ */ c("th", {
241
+ className: "py-1 pr-2 text-right font-normal",
242
+ children: t("planmagnet.analytics.budget.planned", "Planned")
243
+ }),
244
+ /* @__PURE__ */ c("th", {
245
+ className: "w-2/5 py-1 font-normal",
246
+ children: t("planmagnet.analytics.budget.usage", "Used")
247
+ })
248
+ ]
249
+ }) }), /* @__PURE__ */ c("tbody", { children: o.map((e) => {
250
+ let t = b(e.plannedAmount, e.spentAmount), n = x(t), r = S[n], i = t === null ? null : Math.round(t * 100);
251
+ return /* @__PURE__ */ l("tr", {
252
+ className: "border-t align-middle",
253
+ children: [
254
+ /* @__PURE__ */ l("td", {
255
+ className: "py-2 pr-2",
256
+ children: [/* @__PURE__ */ c("span", {
257
+ className: "font-medium",
258
+ children: e.name
259
+ }), e.project ? /* @__PURE__ */ c("span", {
260
+ className: "block text-xs text-muted-foreground",
261
+ children: e.project.name
262
+ }) : null]
263
+ }),
264
+ /* @__PURE__ */ c("td", {
265
+ className: "py-2 pr-2 text-right tabular-nums",
266
+ children: C(e.spentAmount, e.currency)
267
+ }),
268
+ /* @__PURE__ */ c("td", {
269
+ className: "py-2 pr-2 text-right tabular-nums",
270
+ children: C(e.plannedAmount, e.currency)
271
+ }),
272
+ /* @__PURE__ */ l("td", {
273
+ className: "py-2",
274
+ children: [/* @__PURE__ */ c("div", {
275
+ role: "meter",
276
+ "aria-valuemin": 0,
277
+ "aria-valuemax": 100,
278
+ "aria-valuenow": i ?? 0,
279
+ "aria-label": `${e.name}: ${i === null ? "no plan" : `${i}%`} ${h[n]}`,
280
+ title: `${C(e.spentAmount, e.currency)} of ${C(e.plannedAmount, e.currency)}`,
281
+ className: `h-2 w-full overflow-hidden rounded-full ${r.track}`,
282
+ children: /* @__PURE__ */ c("div", {
283
+ className: `h-full rounded-full ${r.fill}`,
284
+ style: { width: `${Math.min(i ?? 0, 100)}%` }
285
+ })
286
+ }), /* @__PURE__ */ l("span", {
287
+ className: `mt-1 flex items-center gap-1 text-xs ${r.text}`,
288
+ children: [/* @__PURE__ */ c(r.Icon, {
289
+ className: "h-3 w-3",
290
+ "aria-hidden": "true"
291
+ }), i === null ? h[n] : `${i}% · ${h[n]}`]
292
+ })]
293
+ })
294
+ ]
295
+ }, e.id);
296
+ }) })]
297
+ }) })] });
298
+ }
299
+ //#endregion
300
+ export { w as BudgetPerformanceCard, y as CycleTimeCard };
301
+
302
+ //# sourceMappingURL=DeliveryHealthCards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeliveryHealthCards.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.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 {\n AlertOctagon,\n AlertTriangle,\n CheckCircle2,\n CircleDashed,\n Timer,\n Wallet,\n} from 'lucide-react';\n\n/**\n * Two analytics the service already computed and no page showed (P1.1).\n *\n * - Cycle time: `planmagnetCycleTime` returned average, median and per-item\n * hours with zero UI consumers.\n * - Budget performance: budgets carry planned / spent / remaining, but the\n * analytics page never surfaced them.\n *\n * Forms follow the dataviz method: headline numbers are stat tiles, per-item\n * detail is a table, and \"spent against a limit\" is a meter whose fill carries\n * severity while its track is a lighter step of the same ramp. Over-budget is a\n * STATUS, so it always ships with an icon and a label — never colour alone.\n * Every colour is a semantic token.\n *\n * Each card owns its query at the top of the component, so neither can land a\n * hook after an early return in the host page.\n */\n\nconst GET_CYCLE_TIME = gql`\n query GetProjectCycleTime($workspaceId: ID!, $projectId: ID!) {\n planmagnetCycleTime(workspaceId: $workspaceId, projectId: $projectId) {\n averageCycleTimeHours\n medianCycleTimeHours\n sampleSize\n items {\n workItemId\n key\n title\n cycleTimeHours\n }\n }\n }\n`;\n\nconst GET_BUDGET_PERFORMANCE = gql`\n query GetBudgetPerformance($workspaceId: ID!) {\n planmagnetBudgets(workspaceId: $workspaceId, pagination: { first: 50 }) {\n items {\n id\n name\n currency\n plannedAmount\n spentAmount\n remainingAmount\n project {\n id\n name\n }\n }\n }\n }\n`;\n\ninterface CycleTimeData {\n planmagnetCycleTime: {\n averageCycleTimeHours: number | null;\n medianCycleTimeHours: number | null;\n sampleSize: number;\n items: Array<{ workItemId: string; key: string; title: string; cycleTimeHours: number }>;\n };\n}\n\ninterface BudgetData {\n planmagnetBudgets: {\n items: Array<{\n id: string;\n name: string;\n currency: string;\n plannedAmount: number;\n spentAmount: number;\n remainingAmount: number;\n project: { id: string; name: string } | null;\n }>;\n };\n}\n\n/** Hours read naturally up to two days; beyond that, days. */\nexport function formatDuration(hours: number | null | undefined): string {\n if (hours === null || hours === undefined || !Number.isFinite(hours)) return '—';\n if (hours < 48) return `${hours < 10 ? hours.toFixed(1) : Math.round(hours)} h`;\n const days = hours / 24;\n return `${days < 10 ? days.toFixed(1) : Math.round(days)} d`;\n}\n\nfunction StatTile({ label, value, hint }: { label: string; value: string; hint?: string }) {\n return (\n <div className=\"rounded-md border p-3\">\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n <p className=\"mt-1 text-2xl font-semibold\">{value}</p>\n {hint ? <p className=\"mt-0.5 text-xs text-muted-foreground\">{hint}</p> : null}\n </div>\n );\n}\n\nexport function CycleTimeCard({\n workspaceId,\n projectId,\n}: {\n workspaceId?: string | null;\n projectId: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<CycleTimeData>(GET_CYCLE_TIME, {\n variables: { workspaceId, projectId },\n skip: !workspaceId || !projectId,\n });\n const cycle = data?.planmagnetCycleTime;\n const slowest = [...(cycle?.items ?? [])]\n .sort((a, b) => b.cycleTimeHours - a.cycleTimeHours)\n .slice(0, 5);\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Timer className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cycleTime.title', 'Cycle time')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.subtitle',\n 'From first moving to In Progress (or creation) to Done, for the selected project.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!projectId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.noProject',\n 'Select a project to see its cycle time.'\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.cycleTime.error', 'Cycle time could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : !cycle || cycle.sampleSize === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.empty',\n 'No completed work items in this project yet — cycle time appears once items reach Done.'\n )}\n </p>\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-3 gap-3\">\n <StatTile\n label={t('planmagnet.analytics.cycleTime.median', 'Median')}\n value={formatDuration(cycle.medianCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.average', 'Average')}\n value={formatDuration(cycle.averageCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.sample', 'Items measured')}\n value={String(cycle.sampleSize)}\n />\n </div>\n <table className=\"w-full text-sm\">\n <caption className=\"mb-2 text-left text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cycleTime.slowest', 'Slowest completed items')}\n </caption>\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.cycleTime.item', 'Item')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.cycleTime.duration', 'Cycle time')}\n </th>\n </tr>\n </thead>\n <tbody>\n {slowest.map((item) => (\n <tr key={item.workItemId} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">\n <span className=\"font-medium\">{item.key}</span>{' '}\n <span className=\"text-muted-foreground\">{item.title}</span>\n </td>\n <td className=\"py-1.5 text-right tabular-nums\">\n {formatDuration(item.cycleTimeHours)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\ntype BudgetState = 'no-plan' | 'on-track' | 'near-limit' | 'over-budget';\n\n/** Share of the plan spent; `null` when there is no plan to measure against. */\nexport function budgetUsage(planned: number, spent: number): number | null {\n if (!Number.isFinite(planned) || planned <= 0) return null;\n return spent / planned;\n}\n\nexport function budgetState(usage: number | null): BudgetState {\n // Without a plan there is nothing to be on track AGAINST — say so, rather\n // than reporting a comforting \"On track\" the numbers cannot support.\n if (usage === null) return 'no-plan';\n if (usage <= 0.85) return 'on-track';\n return usage <= 1 ? 'near-limit' : 'over-budget';\n}\n\nconst STATE_STYLE: Record<\n BudgetState,\n { track: string; fill: string; Icon: typeof CheckCircle2; text: string }\n> = {\n 'no-plan': {\n track: 'bg-muted',\n fill: 'bg-muted',\n Icon: CircleDashed,\n text: 'text-muted-foreground',\n },\n 'on-track': {\n track: 'bg-primary/15',\n fill: 'bg-primary',\n Icon: CheckCircle2,\n text: 'text-muted-foreground',\n },\n 'near-limit': {\n track: 'bg-status-warning-subtle',\n fill: 'bg-status-warning',\n Icon: AlertTriangle,\n text: 'text-status-warning-text',\n },\n 'over-budget': {\n track: 'bg-status-error-subtle',\n fill: 'bg-status-error',\n Icon: AlertOctagon,\n text: 'text-status-error-text',\n },\n};\n\nfunction money(amount: number, currency: string): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(amount);\n } catch {\n // An unrecognised currency code must not blank the whole table.\n return `${Math.round(amount).toLocaleString()} ${currency}`;\n }\n}\n\nexport function BudgetPerformanceCard({ workspaceId }: { workspaceId?: string | null }) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<BudgetData>(GET_BUDGET_PERFORMANCE, {\n variables: { workspaceId },\n skip: !workspaceId,\n });\n const budgets = data?.planmagnetBudgets?.items ?? [];\n const label: Record<BudgetState, string> = {\n 'no-plan': t('planmagnet.analytics.budget.noPlan', 'No plan set'),\n 'on-track': t('planmagnet.analytics.budget.onTrack', 'On track'),\n 'near-limit': t('planmagnet.analytics.budget.nearLimit', 'Near limit'),\n 'over-budget': t('planmagnet.analytics.budget.over', 'Over budget'),\n };\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Wallet className=\"h-4 w-4\" />\n {t('planmagnet.analytics.budget.title', 'Budget performance')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.budget.subtitle',\n 'Spent against plan, per budget, across the workspace.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.budget.error', 'Budgets could not be loaded')}: {error.message}\n </p>\n ) : budgets.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.budget.empty', 'No budgets in this workspace yet.')}\n </p>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.budget.name', 'Budget')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.spent', 'Spent')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.planned', 'Planned')}\n </th>\n <th className=\"w-2/5 py-1 font-normal\">\n {t('planmagnet.analytics.budget.usage', 'Used')}\n </th>\n </tr>\n </thead>\n <tbody>\n {budgets.map((budget) => {\n const usage = budgetUsage(budget.plannedAmount, budget.spentAmount);\n const state = budgetState(usage);\n const style = STATE_STYLE[state];\n const percent = usage === null ? null : Math.round(usage * 100);\n return (\n <tr key={budget.id} className=\"border-t align-middle\">\n <td className=\"py-2 pr-2\">\n <span className=\"font-medium\">{budget.name}</span>\n {budget.project ? (\n <span className=\"block text-xs text-muted-foreground\">\n {budget.project.name}\n </span>\n ) : null}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.spentAmount, budget.currency)}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.plannedAmount, budget.currency)}\n </td>\n <td className=\"py-2\">\n <div\n role=\"meter\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={percent ?? 0}\n aria-label={`${budget.name}: ${percent === null ? 'no plan' : `${percent}%`} ${label[state]}`}\n title={`${money(budget.spentAmount, budget.currency)} of ${money(budget.plannedAmount, budget.currency)}`}\n className={`h-2 w-full overflow-hidden rounded-full ${style.track}`}\n >\n <div\n className={`h-full rounded-full ${style.fill}`}\n style={{ width: `${Math.min(percent ?? 0, 100)}%` }}\n />\n </div>\n <span className={`mt-1 flex items-center gap-1 text-xs ${style.text}`}>\n <style.Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {percent === null ? label[state] : `${percent}% · ${label[state]}`}\n </span>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.924.3",
3
+ "version": "2026.924.4",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [