@burdenoff/microfe-planmagnet 2026.1001.1 → 2026.1001.3

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.
Files changed (122) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +163 -150
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +124 -108
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/comments/CommentThread.js +36 -36
  8. package/dist/planmagnet/comments/RoadmapComments.js +21 -21
  9. package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
  10. package/dist/planmagnet/controls/FeatureControls.js +60 -60
  11. package/dist/planmagnet/controls/MilestoneControls.d.ts +2 -0
  12. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
  13. package/dist/planmagnet/controls/MilestoneControls.js +89 -72
  14. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
  15. package/dist/planmagnet/controls/SyncControls.js +59 -59
  16. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
  17. package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
  18. package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
  19. package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
  20. package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
  21. package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
  22. package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
  23. package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
  24. package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
  25. package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
  26. package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
  27. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
  28. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
  29. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
  30. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
  31. package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
  32. package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
  33. package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
  34. package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
  35. package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
  36. package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
  37. package/dist/planmagnet/forecast/riskFlags.js +37 -0
  38. package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
  39. package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts +17 -0
  40. package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts.map +1 -0
  41. package/dist/planmagnet/meetings/MeetingActionItemsPanel.js +218 -0
  42. package/dist/planmagnet/meetings/MeetingActionItemsPanel.js.map +1 -0
  43. package/dist/planmagnet/meetings/MeetingsCard.d.ts +7 -0
  44. package/dist/planmagnet/meetings/MeetingsCard.d.ts.map +1 -0
  45. package/dist/planmagnet/meetings/MeetingsCard.js +108 -0
  46. package/dist/planmagnet/meetings/MeetingsCard.js.map +1 -0
  47. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts +9 -0
  48. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts.map +1 -0
  49. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js +232 -0
  50. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js.map +1 -0
  51. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts +19 -0
  52. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -0
  53. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +226 -0
  54. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -0
  55. package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts +14 -0
  56. package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts.map +1 -0
  57. package/dist/planmagnet/meetings/UnlinkMeetingDialog.js +81 -0
  58. package/dist/planmagnet/meetings/UnlinkMeetingDialog.js.map +1 -0
  59. package/dist/planmagnet/meetings/meetingActions.d.ts +57 -0
  60. package/dist/planmagnet/meetings/meetingActions.d.ts.map +1 -0
  61. package/dist/planmagnet/meetings/meetingActions.js +51 -0
  62. package/dist/planmagnet/meetings/meetingActions.js.map +1 -0
  63. package/dist/planmagnet/meetings/meetingGraphql.d.ts +29 -0
  64. package/dist/planmagnet/meetings/meetingGraphql.d.ts.map +1 -0
  65. package/dist/planmagnet/meetings/meetingGraphql.js +166 -0
  66. package/dist/planmagnet/meetings/meetingGraphql.js.map +1 -0
  67. package/dist/planmagnet/meetings/meetingModel.d.ts +74 -0
  68. package/dist/planmagnet/meetings/meetingModel.d.ts.map +1 -0
  69. package/dist/planmagnet/meetings/meetingModel.js +88 -0
  70. package/dist/planmagnet/meetings/meetingModel.js.map +1 -0
  71. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  72. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
  73. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  74. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
  75. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
  76. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
  77. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
  79. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts +2 -0
  81. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts.map +1 -0
  82. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js +197 -0
  83. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js.map +1 -0
  84. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts +2 -0
  85. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts.map +1 -0
  86. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js +173 -0
  87. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js.map +1 -0
  88. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
  89. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
  90. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
  91. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
  92. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
  93. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +73 -52
  94. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
  95. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  96. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +205 -190
  97. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  98. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  99. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +211 -182
  100. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  101. package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
  102. package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
  103. package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
  104. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
  105. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
  106. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
  107. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
  108. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
  109. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
  110. package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
  111. package/dist/planmagnet/product/ProductControls.js +91 -91
  112. package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
  113. package/dist/planmagnet/reports/ShareDialog.js +76 -76
  114. package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
  115. package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
  116. package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
  117. package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
  118. package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
  119. package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
  120. package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
  121. package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
  122. package/package.json +1 -1
@@ -0,0 +1,234 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { isForbidden as n } from "../utils/graphqlErrors.js";
3
+ import { enumLabel as r } from "../controls/WorkItemControls.js";
4
+ import { ROW_LINK_CLASS as i, rowLinkProps as a } from "../utils/rowNavigation.js";
5
+ import { ProjectSelect as o } from "../controls/Pickers.js";
6
+ import { RISK_FLAG_KIND_ORDER as s, riskFlagKindIcon as c, riskFlagSeverityBadgeVariant as l } from "../forecast/riskFlags.js";
7
+ import { useState as u } from "react";
8
+ import { ShieldAlert as d } from "lucide-react";
9
+ import { useSearchParams as f } from "react-router";
10
+ import { gql as p } from "@apollo/client";
11
+ import { useQuery as m } from "@apollo/client/react";
12
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
13
+ import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
+ import { Badge as y, Card as b, CardContent as x, CardHeader as S, CardTitle as C } from "@burdenoff/fe-libs/ui";
15
+ //#region src/planmagnet/pages/ProjectsAtRiskPagePage.tsx
16
+ var w = 100, T = p`
17
+ query AtRiskProjectSprints($workspaceId: ID!, $projectId: String) {
18
+ planmagnetSprints(
19
+ workspaceId: $workspaceId
20
+ filter: { projectId: $projectId }
21
+ pagination: { first: 100 }
22
+ ) {
23
+ items {
24
+ id
25
+ name
26
+ status
27
+ }
28
+ }
29
+ }
30
+ `, E = p`
31
+ query GetAtRiskWorkItems(
32
+ $workspaceId: ID!
33
+ $projectId: ID!
34
+ $sprintId: ID
35
+ $pagination: PlanmagnetPaginationInput
36
+ ) {
37
+ planmagnetAtRiskWorkItems(
38
+ workspaceId: $workspaceId
39
+ projectId: $projectId
40
+ sprintId: $sprintId
41
+ pagination: $pagination
42
+ ) {
43
+ items {
44
+ workItem {
45
+ id
46
+ key
47
+ title
48
+ status
49
+ priority
50
+ storyPoints
51
+ }
52
+ flags {
53
+ kind
54
+ severity
55
+ reason
56
+ }
57
+ }
58
+ totalCount
59
+ }
60
+ }
61
+ `, D = "ALL";
62
+ function O() {
63
+ let { workspaceId: p } = e(), O = t(), { t: k } = v(), [A, j] = f(), M = A.get("projectId") ?? "", N = A.get("sprintId") ?? "", [P, F] = u(D);
64
+ function I(e) {
65
+ let t = new URLSearchParams(A);
66
+ e ? t.set("projectId", e) : t.delete("projectId"), t.delete("sprintId"), j(t, { replace: !0 });
67
+ }
68
+ function L(e) {
69
+ let t = new URLSearchParams(A);
70
+ e ? t.set("sprintId", e) : t.delete("sprintId"), j(t, { replace: !0 });
71
+ }
72
+ let { data: R } = m(T, {
73
+ variables: {
74
+ workspaceId: p,
75
+ projectId: M
76
+ },
77
+ skip: !p || !M
78
+ }), z = R?.planmagnetSprints.items ?? [], { data: B, loading: V, error: H } = m(E, {
79
+ variables: {
80
+ workspaceId: p,
81
+ projectId: M,
82
+ sprintId: N || void 0,
83
+ pagination: { first: w }
84
+ },
85
+ skip: !p || !M
86
+ }), U = B?.planmagnetAtRiskWorkItems, W = U?.items ?? [], G = P === D ? W : W.filter((e) => e.flags.some((e) => e.kind === P)), K = /* @__PURE__ */ new Map();
87
+ for (let e of W) for (let t of e.flags) K.set(t.kind, (K.get(t.kind) ?? 0) + 1);
88
+ return /* @__PURE__ */ _("div", {
89
+ className: "space-y-6 p-6",
90
+ "data-testid": "at-risk-page",
91
+ children: [
92
+ /* @__PURE__ */ _("div", {
93
+ className: "flex flex-wrap items-start justify-between gap-2",
94
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("h1", {
95
+ className: "text-2xl font-semibold flex items-center gap-2",
96
+ children: [/* @__PURE__ */ g(d, { className: "h-5 w-5 text-primary" }), k("planmagnet.forecast.atRisk.title", "At-risk work")]
97
+ }), /* @__PURE__ */ g("p", {
98
+ className: "text-sm text-muted-foreground",
99
+ children: k("planmagnet.forecast.atRisk.subtitle", "Open work items carrying one or more risk flags, each raised by an explicit threshold rule — never AI-generated.")
100
+ })] }), /* @__PURE__ */ g(y, {
101
+ variant: "outline",
102
+ children: k("planmagnet.forecast.atRisk.method", "Rule-based")
103
+ })]
104
+ }),
105
+ /* @__PURE__ */ g(b, { children: /* @__PURE__ */ _(x, {
106
+ className: "flex flex-wrap items-end gap-3 pt-4",
107
+ children: [/* @__PURE__ */ _("div", {
108
+ className: "space-y-1",
109
+ children: [/* @__PURE__ */ g("label", {
110
+ htmlFor: "at-risk-project",
111
+ className: "text-xs font-medium text-muted-foreground",
112
+ children: k("planmagnet.forecast.atRisk.project", "Project")
113
+ }), /* @__PURE__ */ g(o, {
114
+ id: "at-risk-project",
115
+ value: M,
116
+ onChange: I,
117
+ noneLabel: k("planmagnet.forecast.atRisk.choose", "Choose a project…")
118
+ })]
119
+ }), M && /* @__PURE__ */ _("div", {
120
+ className: "space-y-1",
121
+ children: [/* @__PURE__ */ g("label", {
122
+ htmlFor: "at-risk-sprint",
123
+ className: "text-xs font-medium text-muted-foreground",
124
+ children: k("planmagnet.forecast.atRisk.sprint", "Sprint (optional)")
125
+ }), /* @__PURE__ */ _("select", {
126
+ id: "at-risk-sprint",
127
+ className: "flex h-9 w-56 rounded-md border border-input bg-background px-3 py-1 text-sm",
128
+ value: N,
129
+ onChange: (e) => L(e.target.value),
130
+ children: [/* @__PURE__ */ g("option", {
131
+ value: "",
132
+ children: k("planmagnet.forecast.atRisk.allSprints", "Every open item (whole project)")
133
+ }), z.map((e) => /* @__PURE__ */ g("option", {
134
+ value: e.id,
135
+ children: e.name
136
+ }, e.id))]
137
+ })]
138
+ })]
139
+ }) }),
140
+ M ? /* @__PURE__ */ _(h, { children: [W.length > 0 && /* @__PURE__ */ _("div", {
141
+ className: "flex flex-wrap items-center gap-1 rounded-md border p-1",
142
+ role: "group",
143
+ "aria-label": k("planmagnet.forecast.atRisk.filterByKind", "Filter by flag kind"),
144
+ children: [/* @__PURE__ */ g("button", {
145
+ type: "button",
146
+ "aria-pressed": P === D,
147
+ onClick: () => F(D),
148
+ className: `px-2.5 py-1 text-xs rounded ${P === D ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
149
+ children: k("planmagnet.forecast.atRisk.all", "All ({{count}})", { count: W.length })
150
+ }), s.filter((e) => (K.get(e) ?? 0) > 0).map((e) => /* @__PURE__ */ _("button", {
151
+ type: "button",
152
+ "aria-pressed": P === e,
153
+ onClick: () => F(e),
154
+ className: `px-2.5 py-1 text-xs rounded ${P === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
155
+ children: [
156
+ r(e),
157
+ " (",
158
+ K.get(e),
159
+ ")"
160
+ ]
161
+ }, e))]
162
+ }), /* @__PURE__ */ _(b, { children: [/* @__PURE__ */ g(S, { children: /* @__PURE__ */ g(C, { children: V && !B ? k("planmagnet.forecast.loading", "Loading…") : k("planmagnet.forecast.atRisk.count", "{{count}} flagged item(s)", { count: G.length }) }) }), /* @__PURE__ */ _(x, {
163
+ className: "space-y-3",
164
+ children: [V && !B ? /* @__PURE__ */ g("div", {
165
+ className: "flex justify-center py-12",
166
+ children: /* @__PURE__ */ g("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
167
+ }) : H ? /* @__PURE__ */ g("p", {
168
+ className: "text-sm text-status-error-text",
169
+ children: n(H) ? k("planmagnet.forecast.atRisk.forbidden", "You do not have access to at-risk work for this project.") : `${k("planmagnet.forecast.atRisk.error", "At-risk work could not be loaded")}: ${H.message}`
170
+ }) : G.length === 0 ? /* @__PURE__ */ g("p", {
171
+ className: "text-sm text-muted-foreground py-8 text-center",
172
+ children: W.length === 0 ? k("planmagnet.forecast.atRisk.empty", "No flagged work items right now.") : k("planmagnet.forecast.atRisk.emptyFilter", "No items match this filter.")
173
+ }) : G.map((e) => /* @__PURE__ */ _("div", {
174
+ "data-testid": "at-risk-item",
175
+ ...a(() => O(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.workItem.id)}`), e.workItem.title),
176
+ className: `rounded-md border p-3 ${i}`,
177
+ children: [/* @__PURE__ */ _("div", {
178
+ className: "flex flex-wrap items-center justify-between gap-2",
179
+ children: [/* @__PURE__ */ _("p", {
180
+ className: "text-sm font-medium",
181
+ children: [
182
+ e.workItem.key,
183
+ " ",
184
+ /* @__PURE__ */ g("span", {
185
+ className: "font-normal",
186
+ children: e.workItem.title
187
+ })
188
+ ]
189
+ }), /* @__PURE__ */ _("span", {
190
+ className: "text-xs text-muted-foreground",
191
+ children: [r(e.workItem.status), e.workItem.storyPoints == null ? "" : ` · ${e.workItem.storyPoints} pts`]
192
+ })]
193
+ }), /* @__PURE__ */ g("div", {
194
+ className: "mt-2 space-y-1.5",
195
+ children: e.flags.map((e) => /* @__PURE__ */ _("div", {
196
+ "data-testid": "at-risk-flag",
197
+ className: "flex items-start gap-2 text-xs",
198
+ children: [
199
+ /* @__PURE__ */ g(c(e.kind), { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
200
+ /* @__PURE__ */ g(y, {
201
+ variant: l(e.severity),
202
+ className: "shrink-0",
203
+ children: r(e.severity)
204
+ }),
205
+ /* @__PURE__ */ g(y, {
206
+ variant: "outline",
207
+ className: "shrink-0",
208
+ children: r(e.kind)
209
+ }),
210
+ /* @__PURE__ */ g("span", {
211
+ className: "text-muted-foreground",
212
+ children: e.reason
213
+ })
214
+ ]
215
+ }, e.kind))
216
+ })]
217
+ }, e.workItem.id)), U && U.totalCount > G.length && P === D && /* @__PURE__ */ g("p", {
218
+ className: "text-xs text-muted-foreground text-center pt-2",
219
+ children: k("planmagnet.forecast.atRisk.moreNote", "Showing the first {{shown}} of {{total}} flagged items.", {
220
+ shown: G.length,
221
+ total: U.totalCount
222
+ })
223
+ })]
224
+ })] })] }) : /* @__PURE__ */ g(b, { children: /* @__PURE__ */ g(x, {
225
+ className: "py-10 text-center text-sm text-muted-foreground",
226
+ children: k("planmagnet.forecast.atRisk.noProject", "Choose a project to see its at-risk work.")
227
+ }) })
228
+ ]
229
+ });
230
+ }
231
+ //#endregion
232
+ export { O as ProjectsAtRiskPagePage };
233
+
234
+ //# sourceMappingURL=ProjectsAtRiskPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsAtRiskPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsAtRiskPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ShieldAlert } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\nimport { ROW_LINK_CLASS, rowLinkProps } from '../utils/rowNavigation';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport {\n RISK_FLAG_KIND_ORDER,\n riskFlagKindIcon,\n riskFlagSeverityBadgeVariant,\n type RiskFlagKindValue,\n type RiskFlagSeverityValue,\n} from '../forecast/riskFlags';\n\n/**\n * AI-10: every open work item of a project (optionally narrowed to one sprint) carrying one or\n * more at-risk flags — the full list `ProjectRiskSummaryCard`'s compact tile links through to.\n * Rule-based (threshold rules over real data), never AI/LLM — see graphql/wspace.graphql's\n * `PlanmagnetRiskFlagKind` for exactly which rule (and threshold) raises each flag kind.\n */\n\nconst AT_RISK_PAGE_SIZE = 100;\n\nconst GET_PROJECT_SPRINTS = gql`\n query AtRiskProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_AT_RISK_WORK_ITEMS = gql`\n query GetAtRiskWorkItems(\n $workspaceId: ID!\n $projectId: ID!\n $sprintId: ID\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetAtRiskWorkItems(\n workspaceId: $workspaceId\n projectId: $projectId\n sprintId: $sprintId\n pagination: $pagination\n ) {\n items {\n workItem {\n id\n key\n title\n status\n priority\n storyPoints\n }\n flags {\n kind\n severity\n reason\n }\n }\n totalCount\n }\n }\n`;\n\ninterface SprintOption {\n id: string;\n name: string;\n status: string;\n}\n\ninterface AtRiskFlag {\n kind: RiskFlagKindValue;\n severity: RiskFlagSeverityValue;\n reason: string;\n}\n\ninterface AtRiskItem {\n workItem: {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n };\n flags: AtRiskFlag[];\n}\n\ninterface AtRiskWorkItemsData {\n planmagnetAtRiskWorkItems: { items: AtRiskItem[]; totalCount: number };\n}\n\nconst KIND_FILTER_ALL = 'ALL' as const;\n\nexport function ProjectsAtRiskPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const { t } = useI18n();\n const [searchParams, setSearchParams] = useSearchParams();\n const projectId = searchParams.get('projectId') ?? '';\n const sprintId = searchParams.get('sprintId') ?? '';\n const [kindFilter, setKindFilter] = useState<RiskFlagKindValue | typeof KIND_FILTER_ALL>(\n KIND_FILTER_ALL\n );\n\n function setProjectId(next: string) {\n const params = new URLSearchParams(searchParams);\n if (next) params.set('projectId', next);\n else params.delete('projectId');\n // A sprint id from the old project would silently scope the new project's query to a\n // sprint it may not even have.\n params.delete('sprintId');\n setSearchParams(params, { replace: true });\n }\n\n function setSprintId(next: string) {\n const params = new URLSearchParams(searchParams);\n if (next) params.set('sprintId', next);\n else params.delete('sprintId');\n setSearchParams(params, { replace: true });\n }\n\n const { data: sprintData } = useQuery<{ planmagnetSprints: { items: SprintOption[] } }>(\n GET_PROJECT_SPRINTS,\n { variables: { workspaceId, projectId }, skip: !workspaceId || !projectId }\n );\n const sprints = sprintData?.planmagnetSprints.items ?? [];\n\n const { data, loading, error } = useQuery<AtRiskWorkItemsData>(GET_AT_RISK_WORK_ITEMS, {\n variables: {\n workspaceId,\n projectId,\n sprintId: sprintId || undefined,\n pagination: { first: AT_RISK_PAGE_SIZE },\n },\n skip: !workspaceId || !projectId,\n });\n const result = data?.planmagnetAtRiskWorkItems;\n const allItems = result?.items ?? [];\n const items =\n kindFilter === KIND_FILTER_ALL\n ? allItems\n : allItems.filter((item) => item.flags.some((f) => f.kind === kindFilter));\n const kindCounts = new Map<RiskFlagKindValue, number>();\n for (const item of allItems) {\n for (const flag of item.flags) {\n kindCounts.set(flag.kind, (kindCounts.get(flag.kind) ?? 0) + 1);\n }\n }\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"at-risk-page\">\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <ShieldAlert className=\"h-5 w-5 text-primary\" />\n {t('planmagnet.forecast.atRisk.title', 'At-risk work')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.forecast.atRisk.subtitle',\n 'Open work items carrying one or more risk flags, each raised by an explicit threshold rule — never AI-generated.'\n )}\n </p>\n </div>\n <Badge variant=\"outline\">{t('planmagnet.forecast.atRisk.method', 'Rule-based')}</Badge>\n </div>\n\n <Card>\n <CardContent className=\"flex flex-wrap items-end gap-3 pt-4\">\n <div className=\"space-y-1\">\n <label htmlFor=\"at-risk-project\" className=\"text-xs font-medium text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.project', 'Project')}\n </label>\n <ProjectSelect\n id=\"at-risk-project\"\n value={projectId}\n onChange={setProjectId}\n noneLabel={t('planmagnet.forecast.atRisk.choose', 'Choose a project…')}\n />\n </div>\n {projectId && (\n <div className=\"space-y-1\">\n <label htmlFor=\"at-risk-sprint\" className=\"text-xs font-medium text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.sprint', 'Sprint (optional)')}\n </label>\n <select\n id=\"at-risk-sprint\"\n className=\"flex h-9 w-56 rounded-md border border-input bg-background px-3 py-1 text-sm\"\n value={sprintId}\n onChange={(e) => setSprintId(e.target.value)}\n >\n <option value=\"\">\n {t('planmagnet.forecast.atRisk.allSprints', 'Every open item (whole project)')}\n </option>\n {sprints.map((s) => (\n <option key={s.id} value={s.id}>\n {s.name}\n </option>\n ))}\n </select>\n </div>\n )}\n </CardContent>\n </Card>\n\n {!projectId ? (\n <Card>\n <CardContent className=\"py-10 text-center text-sm text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.noProject', 'Choose a project to see its at-risk work.')}\n </CardContent>\n </Card>\n ) : (\n <>\n {allItems.length > 0 && (\n <div\n className=\"flex flex-wrap items-center gap-1 rounded-md border p-1\"\n role=\"group\"\n aria-label={t('planmagnet.forecast.atRisk.filterByKind', 'Filter by flag kind')}\n >\n <button\n type=\"button\"\n aria-pressed={kindFilter === KIND_FILTER_ALL}\n onClick={() => setKindFilter(KIND_FILTER_ALL)}\n className={`px-2.5 py-1 text-xs rounded ${\n kindFilter === KIND_FILTER_ALL\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.forecast.atRisk.all', 'All ({{count}})', { count: allItems.length })}\n </button>\n {RISK_FLAG_KIND_ORDER.filter((k) => (kindCounts.get(k) ?? 0) > 0).map((k) => (\n <button\n key={k}\n type=\"button\"\n aria-pressed={kindFilter === k}\n onClick={() => setKindFilter(k)}\n className={`px-2.5 py-1 text-xs rounded ${\n kindFilter === k\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {enumLabel(k)} ({kindCounts.get(k)})\n </button>\n ))}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>\n {loading && !data\n ? t('planmagnet.forecast.loading', 'Loading…')\n : t('planmagnet.forecast.atRisk.count', '{{count}} flagged item(s)', {\n count: items.length,\n })}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {loading && !data ? (\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 ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {isForbidden(error)\n ? t(\n 'planmagnet.forecast.atRisk.forbidden',\n 'You do not have access to at-risk work for this project.'\n )\n : `${t('planmagnet.forecast.atRisk.error', 'At-risk work could not be loaded')}: ${error.message}`}\n </p>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {allItems.length === 0\n ? t('planmagnet.forecast.atRisk.empty', 'No flagged work items right now.')\n : t('planmagnet.forecast.atRisk.emptyFilter', 'No items match this filter.')}\n </p>\n ) : (\n items.map((item) => (\n <div\n key={item.workItem.id}\n data-testid=\"at-risk-item\"\n {...rowLinkProps(\n () =>\n navigate(\n `/tasks/task-detail-page?taskId=${encodeURIComponent(item.workItem.id)}`\n ),\n item.workItem.title\n )}\n className={`rounded-md border p-3 ${ROW_LINK_CLASS}`}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">\n {item.workItem.key}{' '}\n <span className=\"font-normal\">{item.workItem.title}</span>\n </p>\n <span className=\"text-xs text-muted-foreground\">\n {enumLabel(item.workItem.status)}\n {item.workItem.storyPoints != null\n ? ` · ${item.workItem.storyPoints} pts`\n : ''}\n </span>\n </div>\n <div className=\"mt-2 space-y-1.5\">\n {item.flags.map((flag) => {\n const Icon = riskFlagKindIcon(flag.kind);\n return (\n <div\n key={flag.kind}\n data-testid=\"at-risk-flag\"\n className=\"flex items-start gap-2 text-xs\"\n >\n <Icon className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <Badge\n variant={riskFlagSeverityBadgeVariant(flag.severity)}\n className=\"shrink-0\"\n >\n {enumLabel(flag.severity)}\n </Badge>\n <Badge variant=\"outline\" className=\"shrink-0\">\n {enumLabel(flag.kind)}\n </Badge>\n <span className=\"text-muted-foreground\">{flag.reason}</span>\n </div>\n );\n })}\n </div>\n </div>\n ))\n )}\n {result && result.totalCount > items.length && kindFilter === KIND_FILTER_ALL && (\n <p className=\"text-xs text-muted-foreground text-center pt-2\">\n {t(\n 'planmagnet.forecast.atRisk.moreNote',\n 'Showing the first {{shown}} of {{total}} flagged items.',\n { shown: items.length, total: result.totalCount }\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsMilestonesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsMilestonesPagePage.tsx"],"names":[],"mappings":"AA4DA,wBAAgB,0BAA0B,4CAuIzC"}
1
+ {"version":3,"file":"ProjectsMilestonesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsMilestonesPagePage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,0BAA0B,4CA6JzC"}
@@ -1,16 +1,17 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { PersonName as n } from "../people/People.js";
3
- import { detailPath as r, rowLinkProps as i } from "../utils/rowNavigation.js";
4
- import { NewMilestoneDialog as a } from "../create/CreateDialogs.js";
5
- import { MilestoneProgressBar as o } from "../milestones/MilestoneControls.js";
6
- import { useState as s } from "react";
7
- import { Calendar as c, Target as l, User as u } from "lucide-react";
8
- import { gql as d } from "@apollo/client";
9
- import { useQuery as f } from "@apollo/client/react";
10
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
11
- import { Badge as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
3
+ import { detailPath as r, rowLinkProps as i, stopRowActivation as a } from "../utils/rowNavigation.js";
4
+ import { ScheduleMeetingDialog as o } from "../meetings/ScheduleMeetingDialog.js";
5
+ import { NewMilestoneDialog as s } from "../create/CreateDialogs.js";
6
+ import { MilestoneProgressBar as c } from "../milestones/MilestoneControls.js";
7
+ import { useState as l } from "react";
8
+ import { Calendar as u, Target as d, User as f } from "lucide-react";
9
+ import { gql as p } from "@apollo/client";
10
+ import { useQuery as m } from "@apollo/client/react";
11
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
12
+ import { Badge as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x } from "@burdenoff/fe-libs/ui";
12
13
  //#region src/planmagnet/pages/ProjectsMilestonesPagePage.tsx
13
- var b = d`
14
+ var S = p`
14
15
  query GetMilestones($workspaceId: ID!, $filter: PlanmagnetMilestoneFilter) {
15
16
  planmagnetMilestones(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {
16
17
  items {
@@ -38,13 +39,13 @@ var b = d`
38
39
  totalCount
39
40
  }
40
41
  }
41
- `, x = {
42
+ `, C = {
42
43
  PENDING: "bg-gray-100 text-gray-800",
43
44
  IN_PROGRESS: "bg-blue-100 text-blue-800",
44
45
  COMPLETED: "bg-green-100 text-green-800",
45
46
  MISSED: "bg-red-100 text-red-800",
46
47
  CANCELLED: "bg-red-100 text-red-800"
47
- }, S = [
48
+ }, w = [
48
49
  "all",
49
50
  "PENDING",
50
51
  "IN_PROGRESS",
@@ -52,93 +53,113 @@ var b = d`
52
53
  "MISSED",
53
54
  "CANCELLED"
54
55
  ];
55
- function C() {
56
- let { workspaceId: d } = e(), C = t(), [w, T] = s("all"), { data: E, loading: D, error: O } = f(b, { variables: {
57
- workspaceId: d ?? "default",
58
- filter: w === "all" ? void 0 : { status: w }
56
+ function T() {
57
+ let { workspaceId: p } = e(), T = t(), [E, D] = l("all"), { data: O, loading: k, error: A } = m(S, { variables: {
58
+ workspaceId: p ?? "default",
59
+ filter: E === "all" ? void 0 : { status: E }
59
60
  } });
60
- if (D && !E) return /* @__PURE__ */ p("div", {
61
+ if (k && !O) return /* @__PURE__ */ h("div", {
61
62
  className: "flex items-center justify-center p-12",
62
- children: /* @__PURE__ */ p("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
63
+ children: /* @__PURE__ */ h("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
63
64
  });
64
- let k = E?.planmagnetMilestones?.items ?? [], A = E?.planmagnetMilestones?.totalCount ?? 0;
65
- return /* @__PURE__ */ m("div", {
65
+ let j = O?.planmagnetMilestones?.items ?? [], M = O?.planmagnetMilestones?.totalCount ?? 0;
66
+ return /* @__PURE__ */ g("div", {
66
67
  className: "space-y-6 p-6",
67
68
  children: [
68
- /* @__PURE__ */ m("div", {
69
+ /* @__PURE__ */ g("div", {
69
70
  className: "flex items-center justify-between",
70
- children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h1", {
71
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
71
72
  className: "text-2xl font-semibold",
72
73
  children: "Milestones"
73
- }), /* @__PURE__ */ p("p", {
74
+ }), /* @__PURE__ */ h("p", {
74
75
  className: "text-sm text-muted-foreground",
75
- children: w === "all" ? `${A} milestones across all projects` : `${A} ${w.toLowerCase().replace("_", " ")} milestones`
76
- })] }), /* @__PURE__ */ p(a, { onCreated: (e) => C(r("/projects/milestone-detail-page", e)) })]
76
+ children: E === "all" ? `${M} milestones across all projects` : `${M} ${E.toLowerCase().replace("_", " ")} milestones`
77
+ })] }), /* @__PURE__ */ h(s, { onCreated: (e) => T(r("/projects/milestone-detail-page", e)) })]
77
78
  }),
78
- /* @__PURE__ */ p("div", {
79
+ /* @__PURE__ */ h("div", {
79
80
  className: "flex gap-1",
80
- children: S.map((e) => /* @__PURE__ */ p("button", {
81
- onClick: () => T(e),
82
- className: `px-3 py-1.5 text-sm rounded-md capitalize ${w === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
81
+ children: w.map((e) => /* @__PURE__ */ h("button", {
82
+ onClick: () => D(e),
83
+ className: `px-3 py-1.5 text-sm rounded-md capitalize ${E === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
83
84
  children: e === "all" ? "All" : e.toLowerCase().replace("_", " ")
84
85
  }, e))
85
86
  }),
86
- O && /* @__PURE__ */ m("div", {
87
+ A && /* @__PURE__ */ g("div", {
87
88
  role: "alert",
88
89
  className: "rounded-md border border-destructive/40 p-4 text-sm text-destructive",
89
- children: ["Milestones could not be loaded: ", O.message]
90
+ children: ["Milestones could not be loaded: ", A.message]
90
91
  }),
91
- /* @__PURE__ */ p("div", {
92
+ /* @__PURE__ */ h("div", {
92
93
  className: "grid grid-cols-1 md:grid-cols-2 gap-4",
93
- children: k.map((e) => /* @__PURE__ */ m(g, {
94
- ...i(() => C(r("/projects/milestone-detail-page", e.id)), e.name),
95
- children: [/* @__PURE__ */ p(v, {
94
+ children: j.map((e) => /* @__PURE__ */ g(v, {
95
+ ...i(() => T(r("/projects/milestone-detail-page", e.id)), e.name),
96
+ children: [/* @__PURE__ */ h(b, {
96
97
  className: "pb-2",
97
- children: /* @__PURE__ */ m("div", {
98
+ children: /* @__PURE__ */ g("div", {
98
99
  className: "flex items-center justify-between",
99
- children: [/* @__PURE__ */ m(y, {
100
+ children: [/* @__PURE__ */ g(x, {
100
101
  className: "text-base flex items-center gap-2",
101
- children: [/* @__PURE__ */ p(l, { className: "h-4 w-4 text-primary" }), e.name]
102
- }), /* @__PURE__ */ p("span", {
103
- className: `text-xs px-2 py-0.5 rounded-full capitalize ${x[e.status] ?? "bg-gray-100"}`,
102
+ children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 text-primary" }), e.name]
103
+ }), /* @__PURE__ */ h("span", {
104
+ className: `text-xs px-2 py-0.5 rounded-full capitalize ${C[e.status] ?? "bg-gray-100"}`,
104
105
  children: e.status.toLowerCase().replace("_", " ")
105
106
  })]
106
107
  })
107
- }), /* @__PURE__ */ m(_, {
108
+ }), /* @__PURE__ */ g(y, {
108
109
  className: "space-y-3",
109
110
  children: [
110
- /* @__PURE__ */ p("p", {
111
+ /* @__PURE__ */ h("p", {
111
112
  className: "text-sm text-muted-foreground",
112
113
  children: e.description ?? "No description"
113
114
  }),
114
- e.workItemProgress && /* @__PURE__ */ p(o, { progress: e.workItemProgress }),
115
- /* @__PURE__ */ m("div", {
115
+ e.workItemProgress && /* @__PURE__ */ h(c, { progress: e.workItemProgress }),
116
+ /* @__PURE__ */ g("div", {
116
117
  className: "flex gap-3 text-xs text-muted-foreground",
117
118
  children: [
118
- /* @__PURE__ */ m("span", {
119
+ /* @__PURE__ */ g("span", {
119
120
  className: "flex items-center gap-1",
120
- children: [/* @__PURE__ */ p(c, { className: "h-3 w-3" }), e.dueDate ? new Date(e.dueDate).toLocaleDateString() : "No due date"]
121
+ children: [/* @__PURE__ */ h(u, { className: "h-3 w-3" }), e.dueDate ? new Date(e.dueDate).toLocaleDateString() : "No due date"]
121
122
  }),
122
- e.isOverdue && /* @__PURE__ */ p(h, {
123
+ e.isOverdue && /* @__PURE__ */ h(_, {
123
124
  variant: "destructive",
124
125
  "data-testid": "milestone-overdue",
125
126
  children: "Overdue"
126
127
  }),
127
- e.ownerId && /* @__PURE__ */ m("span", {
128
+ e.ownerId && /* @__PURE__ */ g("span", {
128
129
  className: "flex items-center gap-1",
129
- children: [/* @__PURE__ */ p(u, { className: "h-3 w-3" }), /* @__PURE__ */ p(n, { id: e.ownerId })]
130
+ children: [/* @__PURE__ */ h(f, { className: "h-3 w-3" }), /* @__PURE__ */ h(n, { id: e.ownerId })]
130
131
  })
131
132
  ]
132
133
  }),
133
- /* @__PURE__ */ m("div", {
134
+ /* @__PURE__ */ g("div", {
134
135
  className: "text-xs text-muted-foreground",
135
136
  children: ["Project: ", e.project?.name ?? "not available"]
137
+ }),
138
+ /* @__PURE__ */ g("div", {
139
+ className: "flex flex-wrap gap-2 border-t pt-2",
140
+ onClick: a,
141
+ "data-testid": "milestone-schedule-actions",
142
+ children: [/* @__PURE__ */ h(o, {
143
+ projectId: e.projectId,
144
+ milestoneId: e.id,
145
+ defaultKind: "MILESTONE_CELEBRATION",
146
+ lockKind: !0,
147
+ defaultTitle: `${e.name} celebration`,
148
+ triggerLabel: "Celebration"
149
+ }), /* @__PURE__ */ h(o, {
150
+ projectId: e.projectId,
151
+ milestoneId: e.id,
152
+ defaultKind: "DEMO",
153
+ lockKind: !0,
154
+ defaultTitle: `${e.name} demo`,
155
+ triggerLabel: "Demo"
156
+ })]
136
157
  })
137
158
  ]
138
159
  })]
139
160
  }, e.id))
140
161
  }),
141
- !O && k.length === 0 && /* @__PURE__ */ p("p", {
162
+ !A && j.length === 0 && /* @__PURE__ */ h("p", {
142
163
  className: "text-center text-muted-foreground py-8",
143
164
  children: "No milestones found."
144
165
  })
@@ -146,6 +167,6 @@ function C() {
146
167
  });
147
168
  }
148
169
  //#endregion
149
- export { C as ProjectsMilestonesPagePage };
170
+ export { T as ProjectsMilestonesPagePage };
150
171
 
151
172
  //# sourceMappingURL=ProjectsMilestonesPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsMilestonesPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsMilestonesPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Target, Calendar, User } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { NewMilestoneDialog } from '../create/CreateDialogs';\nimport { type MilestoneProgress, MilestoneProgressBar } from '../milestones/MilestoneControls';\n\nconst GET_MILESTONES = gql`\n query GetMilestones($workspaceId: ID!, $filter: PlanmagnetMilestoneFilter) {\n planmagnetMilestones(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n description\n projectId\n project {\n id\n name\n }\n status\n dueDate\n isOverdue\n ownerId\n createdAt\n workItemProgress {\n total\n done\n cancelled\n open\n percentComplete\n }\n }\n totalCount\n }\n }\n`;\n\nconst statusColor: Record<string, string> = {\n PENDING: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n COMPLETED: 'bg-green-100 text-green-800',\n MISSED: 'bg-red-100 text-red-800',\n CANCELLED: 'bg-red-100 text-red-800',\n};\n\nconst milestoneStatuses = [\n 'all',\n 'PENDING',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'MISSED',\n 'CANCELLED',\n] as const;\n\nexport function ProjectsMilestonesPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [filter, setFilter] = useState<string>('all');\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_MILESTONES, {\n variables: {\n workspaceId: workspaceId ?? 'default',\n filter: filter !== 'all' ? { status: filter } : undefined,\n },\n });\n\n // Only before the first load: a refetch after a create must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n // A failed load is shown INSIDE the list area: an early return here replaced the whole page, so the status tabs\n // and New Milestone vanished and a failing tab could not be left (BOFF-7942).\n\n const milestones = data?.planmagnetMilestones?.items ?? [];\n const totalCount = data?.planmagnetMilestones?.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Milestones</h1>\n <p className=\"text-sm text-muted-foreground\">\n {/* The count follows the tab: it is the filtered total, so do not call it \"all\". */}\n {filter === 'all'\n ? `${totalCount} milestones across all projects`\n : `${totalCount} ${filter.toLowerCase().replace('_', ' ')} milestones`}\n </p>\n </div>\n <NewMilestoneDialog\n onCreated={(id) => navigate(detailPath('/projects/milestone-detail-page', id))}\n />\n </div>\n <div className=\"flex gap-1\">\n {milestoneStatuses.map((s) => (\n <button\n key={s}\n onClick={() => setFilter(s)}\n className={`px-3 py-1.5 text-sm rounded-md capitalize ${filter === s ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {s === 'all' ? 'All' : s.toLowerCase().replace('_', ' ')}\n </button>\n ))}\n </div>\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 p-4 text-sm text-destructive\"\n >\n Milestones could not be loaded: {error.message}\n </div>\n )}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {milestones.map(\n (ms: {\n id: string;\n name: string;\n description: string | null;\n projectId: string;\n project: { id: string; name: string } | null;\n status: string;\n dueDate: string | null;\n isOverdue: boolean;\n ownerId: string | null;\n workItemProgress: MilestoneProgress | null;\n }) => (\n <Card\n key={ms.id}\n {...rowLinkProps(\n () => navigate(detailPath('/projects/milestone-detail-page', ms.id)),\n ms.name\n )}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Target className=\"h-4 w-4 text-primary\" />\n {ms.name}\n </CardTitle>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[ms.status] ?? 'bg-gray-100'}`}\n >\n {ms.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {ms.description ?? 'No description'}\n </p>\n {ms.workItemProgress && <MilestoneProgressBar progress={ms.workItemProgress} />}\n <div className=\"flex gap-3 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n {ms.dueDate ? new Date(ms.dueDate).toLocaleDateString() : 'No due date'}\n </span>\n {ms.isOverdue && (\n <Badge variant=\"destructive\" data-testid=\"milestone-overdue\">\n Overdue\n </Badge>\n )}\n {ms.ownerId && (\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" />\n <PersonName id={ms.ownerId} />\n </span>\n )}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n Project: {ms.project?.name ?? 'not available'}\n </div>\n </CardContent>\n </Card>\n )\n )}\n </div>\n {!error && milestones.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">No milestones found.</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectsMilestonesPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsMilestonesPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Target, Calendar, User } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath, stopRowActivation } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { NewMilestoneDialog } from '../create/CreateDialogs';\nimport { type MilestoneProgress, MilestoneProgressBar } from '../milestones/MilestoneControls';\nimport { ScheduleMeetingDialog } from '../meetings/ScheduleMeetingDialog';\n\nconst GET_MILESTONES = gql`\n query GetMilestones($workspaceId: ID!, $filter: PlanmagnetMilestoneFilter) {\n planmagnetMilestones(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n description\n projectId\n project {\n id\n name\n }\n status\n dueDate\n isOverdue\n ownerId\n createdAt\n workItemProgress {\n total\n done\n cancelled\n open\n percentComplete\n }\n }\n totalCount\n }\n }\n`;\n\nconst statusColor: Record<string, string> = {\n PENDING: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n COMPLETED: 'bg-green-100 text-green-800',\n MISSED: 'bg-red-100 text-red-800',\n CANCELLED: 'bg-red-100 text-red-800',\n};\n\nconst milestoneStatuses = [\n 'all',\n 'PENDING',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'MISSED',\n 'CANCELLED',\n] as const;\n\nexport function ProjectsMilestonesPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [filter, setFilter] = useState<string>('all');\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_MILESTONES, {\n variables: {\n workspaceId: workspaceId ?? 'default',\n filter: filter !== 'all' ? { status: filter } : undefined,\n },\n });\n\n // Only before the first load: a refetch after a create must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n // A failed load is shown INSIDE the list area: an early return here replaced the whole page, so the status tabs\n // and New Milestone vanished and a failing tab could not be left (BOFF-7942).\n\n const milestones = data?.planmagnetMilestones?.items ?? [];\n const totalCount = data?.planmagnetMilestones?.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Milestones</h1>\n <p className=\"text-sm text-muted-foreground\">\n {/* The count follows the tab: it is the filtered total, so do not call it \"all\". */}\n {filter === 'all'\n ? `${totalCount} milestones across all projects`\n : `${totalCount} ${filter.toLowerCase().replace('_', ' ')} milestones`}\n </p>\n </div>\n <NewMilestoneDialog\n onCreated={(id) => navigate(detailPath('/projects/milestone-detail-page', id))}\n />\n </div>\n <div className=\"flex gap-1\">\n {milestoneStatuses.map((s) => (\n <button\n key={s}\n onClick={() => setFilter(s)}\n className={`px-3 py-1.5 text-sm rounded-md capitalize ${filter === s ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {s === 'all' ? 'All' : s.toLowerCase().replace('_', ' ')}\n </button>\n ))}\n </div>\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 p-4 text-sm text-destructive\"\n >\n Milestones could not be loaded: {error.message}\n </div>\n )}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {milestones.map(\n (ms: {\n id: string;\n name: string;\n description: string | null;\n projectId: string;\n project: { id: string; name: string } | null;\n status: string;\n dueDate: string | null;\n isOverdue: boolean;\n ownerId: string | null;\n workItemProgress: MilestoneProgress | null;\n }) => (\n <Card\n key={ms.id}\n {...rowLinkProps(\n () => navigate(detailPath('/projects/milestone-detail-page', ms.id)),\n ms.name\n )}\n >\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Target className=\"h-4 w-4 text-primary\" />\n {ms.name}\n </CardTitle>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[ms.status] ?? 'bg-gray-100'}`}\n >\n {ms.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {ms.description ?? 'No description'}\n </p>\n {ms.workItemProgress && <MilestoneProgressBar progress={ms.workItemProgress} />}\n <div className=\"flex gap-3 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n {ms.dueDate ? new Date(ms.dueDate).toLocaleDateString() : 'No due date'}\n </span>\n {ms.isOverdue && (\n <Badge variant=\"destructive\" data-testid=\"milestone-overdue\">\n Overdue\n </Badge>\n )}\n {ms.ownerId && (\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" />\n <PersonName id={ms.ownerId} />\n </span>\n )}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n Project: {ms.project?.name ?? 'not available'}\n </div>\n <div\n className=\"flex flex-wrap gap-2 border-t pt-2\"\n onClick={stopRowActivation}\n data-testid=\"milestone-schedule-actions\"\n >\n <ScheduleMeetingDialog\n projectId={ms.projectId}\n milestoneId={ms.id}\n defaultKind=\"MILESTONE_CELEBRATION\"\n lockKind\n defaultTitle={`${ms.name} celebration`}\n triggerLabel=\"Celebration\"\n />\n <ScheduleMeetingDialog\n projectId={ms.projectId}\n milestoneId={ms.id}\n defaultKind=\"DEMO\"\n lockKind\n defaultTitle={`${ms.name} demo`}\n triggerLabel=\"Demo\"\n />\n </div>\n </CardContent>\n </Card>\n )\n )}\n </div>\n {!error && milestones.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">No milestones found.</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,8BAA8B,4CAwU7C"}
1
+ {"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"AA8FA,wBAAgB,8BAA8B,4CA+V7C"}