@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,218 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { mutationErrorMessage as t } from "../utils/graphqlErrors.js";
3
+ import { throwOnError as n } from "../utils/mutationResult.js";
4
+ import { ErrorLine as r } from "../controls/ConfirmButton.js";
5
+ import { enumLabel as i } from "../controls/WorkItemControls.js";
6
+ import { WorkItemSearchSelect as a } from "../controls/Pickers.js";
7
+ import { ADD_MEETING_ACTION_ITEM as o, CREATE_MEETING_ACTION_WORK_ITEM as s, MEETING_QUERIES as c, REMOVE_MEETING_ACTION_ITEM as l } from "./meetingGraphql.js";
8
+ import { useState as u } from "react";
9
+ import { ListChecks as d, Plus as f, X as p } from "lucide-react";
10
+ import { useMutation as m } from "@apollo/client/react";
11
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
12
+ import { Badge as _, Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
13
+ //#region src/planmagnet/meetings/MeetingActionItemsPanel.tsx
14
+ var w = [
15
+ "TASK",
16
+ "BUG",
17
+ "STORY",
18
+ "SUB_TASK",
19
+ "EPIC"
20
+ ], T = {
21
+ refetchQueries: c,
22
+ awaitRefetchQueries: !0
23
+ };
24
+ function E({ meetingId: c, projectId: E, actionItems: D }) {
25
+ let O = e(), [k, A] = m(o, T), [j, M] = m(l, T), [N, P] = m(s, T), [F, I] = u("none"), [L, R] = u(""), [z, B] = u(""), [V, H] = u("TASK"), [U, W] = u(null), G = A.loading || M.loading || P.loading, K = D.filter((e) => e.workItem), q = K.map((e) => e.workItem.id), J = () => {
26
+ I("none"), R(""), B(""), W(null);
27
+ }, Y = async () => {
28
+ W(null);
29
+ try {
30
+ await n(k({ variables: {
31
+ meetingId: c,
32
+ workItemId: L
33
+ } })), J();
34
+ } catch (e) {
35
+ W(t(e, "The item could not be attached."));
36
+ }
37
+ }, X = async () => {
38
+ W(null);
39
+ let e = z.trim();
40
+ if (!e) {
41
+ W("A title is required.");
42
+ return;
43
+ }
44
+ let r;
45
+ try {
46
+ if (r = (await n(N({ variables: { input: {
47
+ title: e,
48
+ type: V,
49
+ projectId: E
50
+ } } }))).data?.planmagnetCreateWorkItem, !r) throw Error("The work item was not created.");
51
+ } catch (e) {
52
+ W(t(e, "The work item could not be created."));
53
+ return;
54
+ }
55
+ try {
56
+ await n(k({ variables: {
57
+ meetingId: c,
58
+ workItemId: r.id
59
+ } })), J();
60
+ } catch (e) {
61
+ W(`${r.key} was created but could not be attached to this meeting: ` + t(e, "unknown reason"));
62
+ }
63
+ }, Z = async (e) => {
64
+ W(null);
65
+ try {
66
+ await n(j({ variables: {
67
+ meetingId: c,
68
+ workItemId: e
69
+ } }));
70
+ } catch (e) {
71
+ W(t(e, "The item could not be removed."));
72
+ }
73
+ };
74
+ return /* @__PURE__ */ g(y, {
75
+ "data-testid": "meeting-action-items",
76
+ children: [/* @__PURE__ */ g(x, {
77
+ className: "flex flex-row items-center justify-between space-y-0",
78
+ children: [/* @__PURE__ */ g(S, {
79
+ className: "flex items-center gap-2",
80
+ children: [
81
+ /* @__PURE__ */ h(d, { className: "h-4 w-4 text-primary" }),
82
+ "Action items (",
83
+ K.length,
84
+ ")"
85
+ ]
86
+ }), F === "none" && /* @__PURE__ */ g("div", {
87
+ className: "flex gap-2",
88
+ children: [/* @__PURE__ */ h(v, {
89
+ size: "sm",
90
+ variant: "outline",
91
+ onClick: () => I("attach"),
92
+ "data-testid": "meeting-action-items-attach-trigger",
93
+ children: "Attach existing"
94
+ }), /* @__PURE__ */ g(v, {
95
+ size: "sm",
96
+ variant: "outline",
97
+ onClick: () => I("create"),
98
+ "data-testid": "meeting-action-items-add-trigger",
99
+ children: [/* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }), "Add action item"]
100
+ })]
101
+ })]
102
+ }), /* @__PURE__ */ g(b, {
103
+ className: "space-y-2",
104
+ children: [
105
+ K.length === 0 && F === "none" && /* @__PURE__ */ h("p", {
106
+ className: "text-sm text-muted-foreground",
107
+ children: "No action items yet."
108
+ }),
109
+ K.map((e) => /* @__PURE__ */ g("div", {
110
+ className: "flex items-center justify-between gap-2 rounded-md border p-2",
111
+ "data-testid": "meeting-action-item-row",
112
+ children: [/* @__PURE__ */ g("button", {
113
+ type: "button",
114
+ className: "min-w-0 truncate text-left text-sm hover:underline",
115
+ onClick: () => O(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.workItem.id)}`),
116
+ children: [
117
+ /* @__PURE__ */ h("span", {
118
+ className: "text-muted-foreground",
119
+ children: e.workItem.key
120
+ }),
121
+ " ",
122
+ e.workItem.title
123
+ ]
124
+ }), /* @__PURE__ */ g("span", {
125
+ className: "flex shrink-0 items-center gap-2",
126
+ children: [/* @__PURE__ */ h(_, {
127
+ variant: "outline",
128
+ className: "text-xs capitalize",
129
+ children: i(e.workItem.status)
130
+ }), /* @__PURE__ */ h(v, {
131
+ size: "icon",
132
+ variant: "ghost",
133
+ "aria-label": `Remove ${e.workItem.key} from this meeting's action items`,
134
+ disabled: G,
135
+ onClick: () => void Z(e.workItem.id),
136
+ children: /* @__PURE__ */ h(p, { className: "h-4 w-4" })
137
+ })]
138
+ })]
139
+ }, e.id)),
140
+ F === "attach" && /* @__PURE__ */ g("div", {
141
+ className: "space-y-2 border-t pt-3",
142
+ "data-testid": "meeting-action-items-attach-form",
143
+ children: [
144
+ /* @__PURE__ */ h(a, {
145
+ id: "meeting-action-item-attach",
146
+ projectId: E,
147
+ value: L,
148
+ onChange: R,
149
+ exclude: q
150
+ }),
151
+ U && /* @__PURE__ */ h(r, { message: U }),
152
+ /* @__PURE__ */ g("div", {
153
+ className: "flex justify-end gap-2",
154
+ children: [/* @__PURE__ */ h(v, {
155
+ size: "sm",
156
+ variant: "ghost",
157
+ onClick: J,
158
+ children: "Cancel"
159
+ }), /* @__PURE__ */ h(v, {
160
+ size: "sm",
161
+ disabled: !L || G,
162
+ onClick: () => void Y(),
163
+ "data-testid": "meeting-action-items-attach-save",
164
+ children: "Attach"
165
+ })]
166
+ })
167
+ ]
168
+ }),
169
+ F === "create" && /* @__PURE__ */ g("div", {
170
+ className: "space-y-2 border-t pt-3",
171
+ "data-testid": "meeting-action-items-create-form",
172
+ children: [
173
+ /* @__PURE__ */ g("div", {
174
+ className: "grid grid-cols-[1fr_auto] gap-2",
175
+ children: [/* @__PURE__ */ h(C, {
176
+ "aria-label": "New action item title",
177
+ placeholder: "What needs doing?",
178
+ value: z,
179
+ onChange: (e) => B(e.target.value),
180
+ "data-testid": "meeting-action-items-new-title"
181
+ }), /* @__PURE__ */ h("select", {
182
+ "aria-label": "Work item type",
183
+ className: "rounded-md border border-input bg-background px-2 py-1 text-sm",
184
+ value: V,
185
+ onChange: (e) => H(e.target.value),
186
+ children: w.map((e) => /* @__PURE__ */ h("option", {
187
+ value: e,
188
+ children: i(e)
189
+ }, e))
190
+ })]
191
+ }),
192
+ U && /* @__PURE__ */ h(r, { message: U }),
193
+ /* @__PURE__ */ g("div", {
194
+ className: "flex justify-end gap-2",
195
+ children: [/* @__PURE__ */ h(v, {
196
+ size: "sm",
197
+ variant: "ghost",
198
+ onClick: J,
199
+ children: "Cancel"
200
+ }), /* @__PURE__ */ h(v, {
201
+ size: "sm",
202
+ disabled: !z.trim() || G,
203
+ onClick: () => void X(),
204
+ "data-testid": "meeting-action-items-create-save",
205
+ children: "Create & attach"
206
+ })]
207
+ })
208
+ ]
209
+ }),
210
+ F === "none" && U && /* @__PURE__ */ h(r, { message: U })
211
+ ]
212
+ })]
213
+ });
214
+ }
215
+ //#endregion
216
+ export { E as MeetingActionItemsPanel };
217
+
218
+ //# sourceMappingURL=MeetingActionItemsPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingActionItemsPanel.js","names":[],"sources":["../../../src/planmagnet/meetings/MeetingActionItemsPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { ListChecks, Plus, X } from 'lucide-react';\nimport { WorkItemSearchSelect } from '../controls/Pickers';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport {\n ADD_MEETING_ACTION_ITEM,\n REMOVE_MEETING_ACTION_ITEM,\n CREATE_MEETING_ACTION_WORK_ITEM,\n MEETING_QUERIES,\n} from './meetingGraphql';\n\n/**\n * A meeting's action items (CWF-22): real work items the meeting produced. The join only records the fact —\n * \"Add action item\" creates a real WorkItem first, then links it; \"Attach existing item\" picks one already in\n * the meeting's own project. Same two-step shape as `IssueLinksPanel`, which this mirrors.\n */\n\nconst WORK_ITEM_TYPES = ['TASK', 'BUG', 'STORY', 'SUB_TASK', 'EPIC'] as const;\n\ninterface ActionItem {\n id: string;\n workItemId: string;\n workItem: { id: string; key: string; title: string; status: string } | null;\n}\n\nconst refetch = { refetchQueries: MEETING_QUERIES, awaitRefetchQueries: true };\n\nexport function MeetingActionItemsPanel({\n meetingId,\n projectId,\n actionItems,\n}: {\n meetingId: string;\n projectId: string;\n actionItems: ActionItem[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [add, addState] = useMutation<{\n planmagnetAddMeetingActionItem: { id: string; workItemId: string };\n }>(ADD_MEETING_ACTION_ITEM, refetch);\n const [remove, removeState] = useMutation<{ planmagnetRemoveMeetingActionItem: boolean }>(\n REMOVE_MEETING_ACTION_ITEM,\n refetch\n );\n const [createWorkItem, createState] = useMutation<{\n planmagnetCreateWorkItem: { id: string; key: string; title: string; status: string };\n }>(CREATE_MEETING_ACTION_WORK_ITEM, refetch);\n const [mode, setMode] = useState<'none' | 'attach' | 'create'>('none');\n const [attachTarget, setAttachTarget] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newType, setNewType] = useState<(typeof WORK_ITEM_TYPES)[number]>('TASK');\n const [error, setError] = useState<string | null>(null);\n const busy = addState.loading || removeState.loading || createState.loading;\n\n const items = actionItems.filter((a) => a.workItem);\n const attachedIds = items.map((a) => a.workItem!.id);\n\n const closeForms = () => {\n setMode('none');\n setAttachTarget('');\n setNewTitle('');\n setError(null);\n };\n\n const attachExisting = async () => {\n setError(null);\n try {\n await throwOnError(add({ variables: { meetingId, workItemId: attachTarget } }));\n closeForms();\n } catch (e) {\n setError(mutationErrorMessage(e, 'The item could not be attached.'));\n }\n };\n\n const createAndAttach = async () => {\n setError(null);\n const title = newTitle.trim();\n if (!title) {\n setError('A title is required.');\n return;\n }\n let created: { id: string; key: string } | undefined;\n try {\n const result = await throwOnError(\n createWorkItem({ variables: { input: { title, type: newType, projectId } } })\n );\n created = result.data?.planmagnetCreateWorkItem;\n if (!created) throw new Error('The work item was not created.');\n } catch (e) {\n setError(mutationErrorMessage(e, 'The work item could not be created.'));\n return;\n }\n try {\n await throwOnError(add({ variables: { meetingId, workItemId: created.id } }));\n closeForms();\n } catch (e) {\n // The work item is real and already saved — say so, rather than leaving the user thinking nothing\n // happened when in fact an unattached item (created.key) now exists in the project.\n setError(\n `${created.key} was created but could not be attached to this meeting: ` +\n mutationErrorMessage(e, 'unknown reason')\n );\n }\n };\n\n const handleRemove = async (workItemId: string) => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { meetingId, workItemId } }));\n } catch (e) {\n setError(mutationErrorMessage(e, 'The item could not be removed.'));\n }\n };\n\n return (\n <Card data-testid=\"meeting-action-items\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4 text-primary\" />\n Action items ({items.length})\n </CardTitle>\n {mode === 'none' && (\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setMode('attach')}\n data-testid=\"meeting-action-items-attach-trigger\"\n >\n Attach existing\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setMode('create')}\n data-testid=\"meeting-action-items-add-trigger\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add action item\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {items.length === 0 && mode === 'none' && (\n <p className=\"text-sm text-muted-foreground\">No action items yet.</p>\n )}\n {items.map((a) => (\n <div\n key={a.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n data-testid=\"meeting-action-item-row\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(a.workItem!.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{a.workItem!.key}</span> {a.workItem!.title}\n </button>\n <span className=\"flex shrink-0 items-center gap-2\">\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {enumLabel(a.workItem!.status)}\n </Badge>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label={`Remove ${a.workItem!.key} from this meeting's action items`}\n disabled={busy}\n onClick={() => void handleRemove(a.workItem!.id)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n ))}\n\n {mode === 'attach' && (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"meeting-action-items-attach-form\">\n <WorkItemSearchSelect\n id=\"meeting-action-item-attach\"\n projectId={projectId}\n value={attachTarget}\n onChange={setAttachTarget}\n exclude={attachedIds}\n />\n {error && <ErrorLine message={error} />}\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={closeForms}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={!attachTarget || busy}\n onClick={() => void attachExisting()}\n data-testid=\"meeting-action-items-attach-save\"\n >\n Attach\n </Button>\n </div>\n </div>\n )}\n\n {mode === 'create' && (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"meeting-action-items-create-form\">\n <div className=\"grid grid-cols-[1fr_auto] gap-2\">\n <Input\n aria-label=\"New action item title\"\n placeholder=\"What needs doing?\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n data-testid=\"meeting-action-items-new-title\"\n />\n <select\n aria-label=\"Work item type\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={newType}\n onChange={(e) => setNewType(e.target.value as (typeof WORK_ITEM_TYPES)[number])}\n >\n {WORK_ITEM_TYPES.map((t) => (\n <option key={t} value={t}>\n {enumLabel(t)}\n </option>\n ))}\n </select>\n </div>\n {error && <ErrorLine message={error} />}\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={closeForms}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={!newTitle.trim() || busy}\n onClick={() => void createAndAttach()}\n data-testid=\"meeting-action-items-create-save\"\n >\n Create &amp; attach\n </Button>\n </div>\n </div>\n )}\n\n {mode === 'none' && error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function MeetingsCard({ workspaceId, projectId, sprintId, }: {
2
+ workspaceId?: string | null;
3
+ projectId: string;
4
+ /** Scopes the card to one sprint's meetings instead of the whole project's. */
5
+ sprintId?: string | null;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=MeetingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/MeetingsCard.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,2CAkGA"}
@@ -0,0 +1,108 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { isForbidden as t } from "../utils/graphqlErrors.js";
3
+ import { enumLabel as n } from "../controls/WorkItemControls.js";
4
+ import { ROW_LINK_CLASS as r, detailPath as i, rowLinkProps as a, stopRowActivation as o } from "../utils/rowNavigation.js";
5
+ import { summarizeRecurrence as s } from "./meetingModel.js";
6
+ import { LIST_PLANMAGNET_MEETINGS as c } from "./meetingGraphql.js";
7
+ import { ScheduleMeetingDialog as l } from "./ScheduleMeetingDialog.js";
8
+ import { useState as u } from "react";
9
+ import { CalendarClock as d, ExternalLink as f, ListChecks as p } from "lucide-react";
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";
13
+ //#region src/planmagnet/meetings/MeetingsCard.tsx
14
+ function S({ workspaceId: S, projectId: C, sprintId: w }) {
15
+ let T = e(), { data: E, loading: D, error: O, refetch: k } = m(c, {
16
+ variables: {
17
+ workspaceId: S,
18
+ filter: w ? {
19
+ projectId: C,
20
+ sprintId: w
21
+ } : { projectId: C },
22
+ pagination: { first: 100 }
23
+ },
24
+ skip: !S || !C
25
+ }), [A] = u(() => Date.now()), j = E?.planmagnetMeetings.items ?? [], M = j.filter((e) => new Date(e.startsAt).getTime() >= A).sort((e, t) => e.startsAt.localeCompare(t.startsAt)), N = j.filter((e) => new Date(e.startsAt).getTime() < A).sort((e, t) => t.startsAt.localeCompare(e.startsAt)), P = [...M, ...N];
26
+ return /* @__PURE__ */ g(v, {
27
+ "data-testid": "meetings-card",
28
+ children: [/* @__PURE__ */ g(b, {
29
+ className: "flex flex-row items-center justify-between space-y-0",
30
+ children: [/* @__PURE__ */ g(x, {
31
+ className: "flex items-center gap-2",
32
+ children: [
33
+ /* @__PURE__ */ h(d, { className: "h-4 w-4 text-primary" }),
34
+ "Meetings (",
35
+ E?.planmagnetMeetings.totalCount ?? j.length,
36
+ ")"
37
+ ]
38
+ }), /* @__PURE__ */ h(l, {
39
+ projectId: C,
40
+ sprintId: w,
41
+ triggerLabel: "Schedule",
42
+ onScheduled: () => void k()
43
+ })]
44
+ }), /* @__PURE__ */ g(y, {
45
+ className: "space-y-2",
46
+ children: [
47
+ D && !E && /* @__PURE__ */ h("p", {
48
+ className: "text-sm text-muted-foreground",
49
+ children: "Loading…"
50
+ }),
51
+ O && /* @__PURE__ */ h("p", {
52
+ role: "alert",
53
+ className: "text-sm text-destructive",
54
+ children: t(O) ? "You don't have access to this project's meetings." : `Meetings could not be loaded: ${O.message}`
55
+ }),
56
+ E && P.length === 0 && /* @__PURE__ */ h("p", {
57
+ className: "text-sm text-muted-foreground text-center py-2",
58
+ children: "No meetings scheduled yet."
59
+ }),
60
+ P.map((e) => /* @__PURE__ */ g("div", {
61
+ ...a(() => T(i("/meetings/meeting-detail-page", e.id)), e.title),
62
+ className: `space-y-1 border-b pb-2 last:border-0 ${r}`,
63
+ "data-testid": "meetings-card-row",
64
+ children: [/* @__PURE__ */ g("div", {
65
+ className: "flex items-center justify-between gap-2",
66
+ children: [/* @__PURE__ */ h("span", {
67
+ className: "text-sm font-medium truncate",
68
+ children: e.title
69
+ }), /* @__PURE__ */ h(_, {
70
+ variant: "outline",
71
+ className: "text-xs shrink-0",
72
+ children: n(e.kind)
73
+ })]
74
+ }), /* @__PURE__ */ g("div", {
75
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
76
+ children: [
77
+ /* @__PURE__ */ h("span", { children: new Date(e.startsAt).toLocaleString(void 0, {
78
+ dateStyle: "medium",
79
+ timeStyle: "short"
80
+ }) }),
81
+ /* @__PURE__ */ h("span", { children: s(e.recurrenceRule) }),
82
+ e.actionItems.length > 0 && /* @__PURE__ */ g("span", {
83
+ className: "inline-flex items-center gap-1",
84
+ children: [
85
+ /* @__PURE__ */ h(p, { className: "h-3 w-3" }),
86
+ e.actionItems.length,
87
+ " action item",
88
+ e.actionItems.length === 1 ? "" : "s"
89
+ ]
90
+ }),
91
+ /* @__PURE__ */ g("a", {
92
+ href: `/calendar/event/${encodeURIComponent(e.calendarEventId)}`,
93
+ onClick: o,
94
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
95
+ "data-testid": "meetings-card-open-in-calendar",
96
+ children: ["Open in calendar", /* @__PURE__ */ h(f, { className: "h-3 w-3" })]
97
+ })
98
+ ]
99
+ })]
100
+ }, e.id))
101
+ ]
102
+ })]
103
+ });
104
+ }
105
+ //#endregion
106
+ export { S as MeetingsCard };
107
+
108
+ //# sourceMappingURL=MeetingsCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsCard.js","names":[],"sources":["../../../src/planmagnet/meetings/MeetingsCard.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, ListChecks } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport {\n ROW_LINK_CLASS,\n rowLinkProps,\n detailPath,\n stopRowActivation,\n} from '../utils/rowNavigation';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { summarizeRecurrence, type MeetingKind } from './meetingModel';\nimport { LIST_PLANMAGNET_MEETINGS } from './meetingGraphql';\nimport { ScheduleMeetingDialog } from './ScheduleMeetingDialog';\n\n/**\n * A project's (or, scoped to one sprint, that sprint's) meetings — upcoming first (GAP-06). Self-fetching, same\n * convention as the other project-detail cards (`ProjectRiskSummaryCard`, `ResourceForecastCard`).\n */\n\ninterface MeetingRow {\n id: string;\n title: string;\n kind: MeetingKind;\n startsAt: string;\n endsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n actionItems: { id: string }[];\n}\n\nexport function MeetingsCard({\n workspaceId,\n projectId,\n sprintId,\n}: {\n workspaceId?: string | null;\n projectId: string;\n /** Scopes the card to one sprint's meetings instead of the whole project's. */\n sprintId?: string | null;\n}) {\n const navigate = usePlanMagnetNavigate();\n const { data, loading, error, refetch } = useQuery<{\n planmagnetMeetings: { items: MeetingRow[]; totalCount: number };\n }>(LIST_PLANMAGNET_MEETINGS, {\n variables: {\n workspaceId,\n filter: sprintId ? { projectId, sprintId } : { projectId },\n pagination: { first: 100 },\n },\n skip: !workspaceId || !projectId,\n });\n\n // Fixed for the life of the card — a refetch (e.g. right after scheduling a meeting) must not shift the\n // upcoming/past split underneath the reader (same fix as `DashboardsExecutivePagePage`'s own `now`).\n const [now] = useState(() => Date.now());\n const items = data?.planmagnetMeetings.items ?? [];\n const upcoming = items\n .filter((m) => new Date(m.startsAt).getTime() >= now)\n .sort((a, b) => a.startsAt.localeCompare(b.startsAt));\n const past = items\n .filter((m) => new Date(m.startsAt).getTime() < now)\n .sort((a, b) => b.startsAt.localeCompare(a.startsAt));\n const ordered = [...upcoming, ...past];\n\n return (\n <Card data-testid=\"meetings-card\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-primary\" />\n Meetings ({data?.planmagnetMeetings.totalCount ?? items.length})\n </CardTitle>\n <ScheduleMeetingDialog\n projectId={projectId}\n sprintId={sprintId}\n triggerLabel=\"Schedule\"\n onScheduled={() => void refetch()}\n />\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {loading && !data && <p className=\"text-sm text-muted-foreground\">Loading…</p>}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {isForbidden(error)\n ? \"You don't have access to this project's meetings.\"\n : `Meetings could not be loaded: ${error.message}`}\n </p>\n )}\n {data && ordered.length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-2\">\n No meetings scheduled yet.\n </p>\n )}\n {ordered.map((m) => (\n <div\n key={m.id}\n {...rowLinkProps(\n () => navigate(detailPath('/meetings/meeting-detail-page', m.id)),\n m.title\n )}\n className={`space-y-1 border-b pb-2 last:border-0 ${ROW_LINK_CLASS}`}\n data-testid=\"meetings-card-row\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium truncate\">{m.title}</span>\n <Badge variant=\"outline\" className=\"text-xs shrink-0\">\n {enumLabel(m.kind)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n <span>\n {new Date(m.startsAt).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n </span>\n <span>{summarizeRecurrence(m.recurrenceRule)}</span>\n {m.actionItems.length > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <ListChecks className=\"h-3 w-3\" />\n {m.actionItems.length} action item{m.actionItems.length === 1 ? '' : 's'}\n </span>\n )}\n <a\n href={`/calendar/event/${encodeURIComponent(m.calendarEventId)}`}\n onClick={stopRowActivation}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n data-testid=\"meetings-card-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiCA,SAAgB,EAAa,EAC3B,gBACA,cACA,eAMC;CACD,IAAM,IAAW,GAAuB,EAClC,EAAE,SAAM,YAAS,UAAO,eAAY,EAEvC,GAA0B;EAC3B,WAAW;GACT;GACA,QAAQ,IAAW;IAAE;IAAW;IAAU,GAAG,EAAE,cAAW;GAC1D,YAAY,EAAE,OAAO,KAAK;GAC3B;EACD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EAII,CAAC,KAAO,QAAe,KAAK,KAAK,CAAC,EAClC,IAAQ,GAAM,mBAAmB,SAAS,EAAE,EAC5C,IAAW,EACd,QAAQ,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,IAAI,EAAI,CACpD,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,CAAC,EACjD,IAAO,EACV,QAAQ,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,EAAI,CACnD,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,CAAC,EACjD,IAAU,CAAC,GAAG,GAAU,GAAG,EAAK;AAEtC,QACE,kBAAC,GAAD;EAAM,eAAY;YAAlB,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,GAAD,EAAe,WAAU,wBAAyB,CAAA;;KACvC,GAAM,mBAAmB,cAAc,EAAM;KAAO;KACrD;OACZ,kBAAC,GAAD;IACa;IACD;IACV,cAAa;IACb,mBAAmB,KAAK,GAAS;IACjC,CAAA,CACS;MACb,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACG,KAAW,CAAC,KAAQ,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAAY,CAAA;IAC7E,KACC,kBAAC,KAAD;KAAG,MAAK;KAAQ,WAAU;eACvB,EAAY,EAAM,GACf,sDACA,iCAAiC,EAAM;KACzC,CAAA;IAEL,KAAQ,EAAQ,WAAW,KAC1B,kBAAC,KAAD;KAAG,WAAU;eAAiD;KAE1D,CAAA;IAEL,EAAQ,KAAK,MACZ,kBAAC,OAAD;KAEE,GAAI,QACI,EAAS,EAAW,iCAAiC,EAAE,GAAG,CAAC,EACjE,EAAE,MACH;KACD,WAAW,yCAAyC;KACpD,eAAY;eAPd,CASE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC,EAAE;OAAa,CAAA,EAC/D,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAU,EAAE,KAAK;OACZ,CAAA,CACJ;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD,EAAA,UACG,IAAI,KAAK,EAAE,SAAS,CAAC,eAAe,KAAA,GAAW;QAC9C,WAAW;QACX,WAAW;QACZ,CAAC,EACG,CAAA;OACP,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAE,eAAe,EAAQ,CAAA;OACnD,EAAE,YAAY,SAAS,KACtB,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;SACjC,EAAE,YAAY;SAAO;SAAa,EAAE,YAAY,WAAW,IAAI,KAAK;SAChE;;OAET,kBAAC,KAAD;QACE,MAAM,mBAAmB,mBAAmB,EAAE,gBAAgB;QAC9D,SAAS;QACT,WAAU;QACV,eAAY;kBAJd,CAKC,oBAEC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAClC;;OACA;QACF;OAtCC,EAAE,GAsCH,CACN;IACU;KACT"}
@@ -0,0 +1,9 @@
1
+ export declare function ScheduleCeremoniesDialog({ projectId, sprintId, startDate, endDate, }: {
2
+ projectId: string;
3
+ sprintId: string;
4
+ /** Planned start/end (UTC-midnight calendar days). Null disables the whole flow — a sprint with no dates has
5
+ * nothing to propose against. */
6
+ startDate: string | null;
7
+ endDate: string | null;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=ScheduleCeremoniesDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScheduleCeremoniesDialog.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/ScheduleCeremoniesDialog.tsx"],"names":[],"mappings":"AA6GA,wBAAgB,wBAAwB,CAAC,EACvC,SAAS,EACT,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;sCACkC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,2CA2PA"}
@@ -0,0 +1,232 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { ErrorLine as n } from "../controls/ConfirmButton.js";
3
+ import { detailPath as r } from "../utils/rowNavigation.js";
4
+ import { DURATION_OPTIONS_MINUTES as i, addMinutesIso as a, localInputValueToISO as o, rruleUntil as s, viewerTimeZone as c } from "./meetingModel.js";
5
+ import { CREATE_CALENDAR_EVENT as l, DELETE_CALENDAR_EVENT as u, LINK_PLANMAGNET_MEETING as d, MEETING_QUERIES as f, SCHEDULE_MEETING_CALENDARS as p } from "./meetingGraphql.js";
6
+ import { createAndLinkMeeting as m, meetingScheduleErrorMessage as h } from "./meetingActions.js";
7
+ import { useState as g } from "react";
8
+ import { CalendarRange as _, CheckCircle2 as v, XCircle as y } from "lucide-react";
9
+ import { useMutation as b, useQuery as x } from "@apollo/client/react";
10
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
11
+ import { Button as w, Input as T, ResponsiveDialog as E, ResponsiveDialogContent as D, ResponsiveDialogDescription as O, ResponsiveDialogFooter as k, ResponsiveDialogHeader as ee, ResponsiveDialogTitle as te, ResponsiveDialogTrigger as ne } from "@burdenoff/fe-libs/ui";
12
+ //#region src/planmagnet/meetings/ScheduleCeremoniesDialog.tsx
13
+ var A = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60";
14
+ function j(e, t) {
15
+ return `${e.slice(0, 10)}T${t}`;
16
+ }
17
+ function M(e, t) {
18
+ return [
19
+ {
20
+ key: "PLANNING",
21
+ label: "Planning",
22
+ kind: "PLANNING",
23
+ startLocal: j(e, "10:00"),
24
+ durationMinutes: 60,
25
+ recurs: !1
26
+ },
27
+ {
28
+ key: "STANDUP",
29
+ label: "Daily standup",
30
+ kind: "STANDUP",
31
+ startLocal: j(e, "09:30"),
32
+ durationMinutes: 15,
33
+ recurs: !0
34
+ },
35
+ {
36
+ key: "REVIEW",
37
+ label: "Review",
38
+ kind: "REVIEW",
39
+ startLocal: j(t, "14:00"),
40
+ durationMinutes: 60,
41
+ recurs: !1
42
+ },
43
+ {
44
+ key: "RETROSPECTIVE",
45
+ label: "Retrospective",
46
+ kind: "RETROSPECTIVE",
47
+ startLocal: j(t, "15:15"),
48
+ durationMinutes: 45,
49
+ recurs: !1
50
+ }
51
+ ];
52
+ }
53
+ function N({ projectId: j, sprintId: N, startDate: P, endDate: F }) {
54
+ let { workspaceId: I } = e(), L = t(), [R, z] = g(!1), [B, V] = g(""), [H, U] = g([]), [W, G] = g(!1), [K, q] = g(null), [J, Y] = g(null), { data: X, loading: Z } = x(p, {
55
+ variables: { pagination: { limit: 100 } },
56
+ skip: !R
57
+ }), re = X?.calendars.calendars ?? [], [ie] = b(l), [ae] = b(d, {
58
+ refetchQueries: f,
59
+ awaitRefetchQueries: !0
60
+ }), [oe] = b(u), Q = !!(P && F), se = (e) => {
61
+ z(e), Y(null), q(null), G(!1), e && P && F && (U(M(P, F)), V(""));
62
+ }, $ = (e, t) => U((n) => n.map((n) => n.key === e ? {
63
+ ...n,
64
+ ...t
65
+ } : n)), ce = async () => {
66
+ if (!I || !B || !P || !F) {
67
+ Y("Pick a calendar first.");
68
+ return;
69
+ }
70
+ Y(null), G(!0);
71
+ let e = [];
72
+ for (let t of H) {
73
+ let n = o(t.startLocal);
74
+ if (!n) {
75
+ e.push({
76
+ key: t.key,
77
+ label: t.label,
78
+ status: "error",
79
+ message: "Invalid start time."
80
+ });
81
+ continue;
82
+ }
83
+ let r = a(n, t.durationMinutes), i = t.recurs ? /* @__PURE__ */ new Date(`${F.slice(0, 10)}T23:59:59.000Z`) : null, l = t.recurs ? `FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;UNTIL=${s(i)}` : void 0;
84
+ try {
85
+ let i = await m({
86
+ createEvent: ie,
87
+ linkMeeting: ae,
88
+ deleteEvent: oe,
89
+ workspaceId: I,
90
+ calendarId: B,
91
+ title: t.label,
92
+ startIso: n,
93
+ endIso: r,
94
+ kind: t.kind,
95
+ projectId: j,
96
+ sprintId: N,
97
+ agenda: "",
98
+ rrule: l,
99
+ timezone: c()
100
+ });
101
+ e.push({
102
+ key: t.key,
103
+ label: t.label,
104
+ status: "success",
105
+ meetingId: i.id
106
+ });
107
+ } catch (n) {
108
+ e.push({
109
+ key: t.key,
110
+ label: t.label,
111
+ status: "error",
112
+ message: h(n)
113
+ });
114
+ }
115
+ }
116
+ q(e), G(!1);
117
+ };
118
+ return /* @__PURE__ */ C(E, {
119
+ open: R,
120
+ onOpenChange: se,
121
+ children: [/* @__PURE__ */ C("div", {
122
+ className: "flex flex-col items-end gap-1",
123
+ children: [/* @__PURE__ */ S(ne, {
124
+ asChild: !0,
125
+ children: /* @__PURE__ */ C(w, {
126
+ size: "sm",
127
+ variant: "outline",
128
+ disabled: !Q,
129
+ "data-testid": "schedule-ceremonies-trigger",
130
+ children: [/* @__PURE__ */ S(_, { className: "mr-1 h-4 w-4" }), "Schedule ceremonies"]
131
+ })
132
+ }), !Q && /* @__PURE__ */ S("span", {
133
+ className: "text-xs text-muted-foreground text-right max-w-[14rem]",
134
+ children: "Set this sprint's start and end dates to propose its ceremonies."
135
+ })]
136
+ }), /* @__PURE__ */ C(D, {
137
+ className: "max-w-xl",
138
+ children: [
139
+ /* @__PURE__ */ C(ee, { children: [/* @__PURE__ */ S(te, { children: "Schedule ceremonies" }), /* @__PURE__ */ S(O, { children: "Planning at the sprint's start, a weekday daily standup through its length, and Review + Retrospective at its end. Edit any time before scheduling." })] }),
140
+ K ? /* @__PURE__ */ S("div", {
141
+ className: "space-y-2",
142
+ "data-testid": "schedule-ceremonies-results",
143
+ children: K.map((e) => /* @__PURE__ */ C("div", {
144
+ className: "flex items-center justify-between gap-2 rounded-md border p-2 text-sm",
145
+ "data-testid": "schedule-ceremonies-result-row",
146
+ children: [/* @__PURE__ */ C("span", {
147
+ className: "flex items-center gap-2",
148
+ children: [e.status === "success" ? /* @__PURE__ */ S(v, { className: "h-4 w-4 text-status-success-text" }) : /* @__PURE__ */ S(y, { className: "h-4 w-4 text-destructive" }), e.label]
149
+ }), e.status === "success" ? /* @__PURE__ */ S("button", {
150
+ type: "button",
151
+ className: "text-primary hover:underline",
152
+ onClick: () => {
153
+ z(!1), L(r("/meetings/meeting-detail-page", e.meetingId));
154
+ },
155
+ children: "View"
156
+ }) : /* @__PURE__ */ S("span", {
157
+ className: "text-destructive text-xs",
158
+ children: e.message
159
+ })]
160
+ }, e.key))
161
+ }) : /* @__PURE__ */ C("div", {
162
+ className: "space-y-3",
163
+ children: [
164
+ /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
165
+ htmlFor: "ceremonies-calendar",
166
+ className: "text-sm font-medium",
167
+ children: "Calendar"
168
+ }), /* @__PURE__ */ C("select", {
169
+ id: "ceremonies-calendar",
170
+ className: A,
171
+ value: B,
172
+ disabled: Z,
173
+ onChange: (e) => V(e.target.value),
174
+ "data-testid": "ceremonies-calendar",
175
+ children: [/* @__PURE__ */ S("option", {
176
+ value: "",
177
+ children: Z ? "Loading calendars…" : "Choose a calendar"
178
+ }), re.map((e) => /* @__PURE__ */ C("option", {
179
+ value: e.id,
180
+ children: [e.name, e.isDefault ? " (default)" : ""]
181
+ }, e.id))]
182
+ })] }),
183
+ H.map((e) => /* @__PURE__ */ C("div", {
184
+ className: "grid grid-cols-[1fr_auto_auto] items-end gap-2",
185
+ children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ C("label", {
186
+ htmlFor: `ceremony-start-${e.key}`,
187
+ className: "text-xs text-muted-foreground",
188
+ children: [e.label, e.recurs ? " (weekdays, first occurrence)" : ""]
189
+ }), /* @__PURE__ */ S(T, {
190
+ id: `ceremony-start-${e.key}`,
191
+ type: "datetime-local",
192
+ className: "mt-1",
193
+ value: e.startLocal,
194
+ onChange: (t) => $(e.key, { startLocal: t.target.value }),
195
+ "data-testid": `ceremony-start-${e.key}`
196
+ })] }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
197
+ htmlFor: `ceremony-duration-${e.key}`,
198
+ className: "text-xs text-muted-foreground",
199
+ children: "Duration"
200
+ }), /* @__PURE__ */ S("select", {
201
+ id: `ceremony-duration-${e.key}`,
202
+ className: A,
203
+ value: e.durationMinutes,
204
+ onChange: (t) => $(e.key, { durationMinutes: Number(t.target.value) }),
205
+ "data-testid": `ceremony-duration-${e.key}`,
206
+ children: i.map((e) => /* @__PURE__ */ C("option", {
207
+ value: e,
208
+ children: [e, "m"]
209
+ }, e))
210
+ })] })]
211
+ }, e.key)),
212
+ J && /* @__PURE__ */ S(n, { message: J })
213
+ ]
214
+ }),
215
+ /* @__PURE__ */ C(k, { children: [/* @__PURE__ */ S(w, {
216
+ variant: "outline",
217
+ onClick: () => z(!1),
218
+ children: K ? "Close" : "Cancel"
219
+ }), !K && /* @__PURE__ */ S(w, {
220
+ onClick: () => void ce(),
221
+ disabled: W || !B,
222
+ "data-testid": "schedule-ceremonies-submit",
223
+ children: W ? "Scheduling…" : `Schedule ${H.length} ceremonies`
224
+ })] })
225
+ ]
226
+ })]
227
+ });
228
+ }
229
+ //#endregion
230
+ export { N as ScheduleCeremoniesDialog };
231
+
232
+ //# sourceMappingURL=ScheduleCeremoniesDialog.js.map