@burdenoff/microfe-planmagnet 2026.1001.2 → 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.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +160 -153
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +121 -109
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.d.ts +2 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.js +89 -72
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts +17 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts.map +1 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.js +218 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.js.map +1 -0
- package/dist/planmagnet/meetings/MeetingsCard.d.ts +7 -0
- package/dist/planmagnet/meetings/MeetingsCard.d.ts.map +1 -0
- package/dist/planmagnet/meetings/MeetingsCard.js +108 -0
- package/dist/planmagnet/meetings/MeetingsCard.js.map +1 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts +9 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js +232 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js.map +1 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts +19 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +226 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts +14 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.js +81 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.js.map +1 -0
- package/dist/planmagnet/meetings/meetingActions.d.ts +57 -0
- package/dist/planmagnet/meetings/meetingActions.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingActions.js +51 -0
- package/dist/planmagnet/meetings/meetingActions.js.map +1 -0
- package/dist/planmagnet/meetings/meetingGraphql.d.ts +29 -0
- package/dist/planmagnet/meetings/meetingGraphql.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingGraphql.js +166 -0
- package/dist/planmagnet/meetings/meetingGraphql.js.map +1 -0
- package/dist/planmagnet/meetings/meetingModel.d.ts +74 -0
- package/dist/planmagnet/meetings/meetingModel.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingModel.js +88 -0
- package/dist/planmagnet/meetings/meetingModel.js.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js +197 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js +173 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +73 -52
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +205 -190
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +192 -187
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/reports/ShareDialog.js +65 -65
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { isForbidden as t, mutationErrorMessage as n } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
4
|
+
import { ErrorLine as i } from "../controls/ConfirmButton.js";
|
|
5
|
+
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
6
|
+
import { detailPath as o } from "../utils/rowNavigation.js";
|
|
7
|
+
import { useEntityId as s } from "../hooks/useEntityId.js";
|
|
8
|
+
import { summarizeRecurrence as c } from "../meetings/meetingModel.js";
|
|
9
|
+
import { GET_PLANMAGNET_MEETING as l, UPDATE_PLANMAGNET_MEETING as u } from "../meetings/meetingGraphql.js";
|
|
10
|
+
import { MeetingActionItemsPanel as d } from "../meetings/MeetingActionItemsPanel.js";
|
|
11
|
+
import { UnlinkMeetingDialog as f } from "../meetings/UnlinkMeetingDialog.js";
|
|
12
|
+
import { useState as p } from "react";
|
|
13
|
+
import { CalendarClock as m, ExternalLink as h, FolderKanban as g, Repeat as _, Target as v, Zap as y } from "lucide-react";
|
|
14
|
+
import { useMutation as b, useQuery as x } from "@apollo/client/react";
|
|
15
|
+
import { jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
16
|
+
import { Badge as w, Button as T, Card as E, CardContent as D, CardHeader as O, CardTitle as k, Textarea as A } from "@burdenoff/fe-libs/ui";
|
|
17
|
+
//#region src/planmagnet/pages/MeetingsMeetingDetailPagePage.tsx
|
|
18
|
+
function j({ id: e, label: t, value: r, placeholder: a, onSave: o }) {
|
|
19
|
+
let [s, c] = p(r ?? ""), [l, u] = p(!1), [d, f] = p(null), m = r ?? "", h = s !== m, g = async () => {
|
|
20
|
+
f(null), u(!0);
|
|
21
|
+
try {
|
|
22
|
+
await o(s.trim());
|
|
23
|
+
} catch (e) {
|
|
24
|
+
f(n(e, `The ${t.toLowerCase()} could not be saved.`));
|
|
25
|
+
} finally {
|
|
26
|
+
u(!1);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return /* @__PURE__ */ C("div", {
|
|
30
|
+
className: "space-y-1",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ S("label", {
|
|
33
|
+
htmlFor: e,
|
|
34
|
+
className: "text-sm font-medium",
|
|
35
|
+
children: t
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ S(A, {
|
|
38
|
+
id: e,
|
|
39
|
+
className: "min-h-[120px]",
|
|
40
|
+
placeholder: a,
|
|
41
|
+
value: s,
|
|
42
|
+
onChange: (e) => c(e.target.value),
|
|
43
|
+
"data-testid": e
|
|
44
|
+
}),
|
|
45
|
+
d && /* @__PURE__ */ S(i, { message: d }),
|
|
46
|
+
h && /* @__PURE__ */ C("div", {
|
|
47
|
+
className: "flex justify-end gap-2",
|
|
48
|
+
children: [/* @__PURE__ */ S(T, {
|
|
49
|
+
size: "sm",
|
|
50
|
+
variant: "ghost",
|
|
51
|
+
onClick: () => c(m),
|
|
52
|
+
disabled: l,
|
|
53
|
+
children: "Cancel"
|
|
54
|
+
}), /* @__PURE__ */ S(T, {
|
|
55
|
+
size: "sm",
|
|
56
|
+
onClick: () => void g(),
|
|
57
|
+
disabled: l,
|
|
58
|
+
"data-testid": `${e}-save`,
|
|
59
|
+
children: l ? "Saving…" : "Save"
|
|
60
|
+
})]
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function M() {
|
|
66
|
+
let n = s("meetingId"), i = e(), { data: p, loading: T, error: A } = x(l, {
|
|
67
|
+
variables: { id: n },
|
|
68
|
+
skip: !n
|
|
69
|
+
}), [M] = b(u, {
|
|
70
|
+
refetchQueries: ["GetPlanmagnetMeeting"],
|
|
71
|
+
awaitRefetchQueries: !0
|
|
72
|
+
});
|
|
73
|
+
if (!n) return /* @__PURE__ */ S("div", {
|
|
74
|
+
className: "p-6 text-muted-foreground",
|
|
75
|
+
children: "No meeting selected. Open one from the list."
|
|
76
|
+
});
|
|
77
|
+
if (T && !p) return /* @__PURE__ */ S("div", {
|
|
78
|
+
className: "flex items-center justify-center p-12",
|
|
79
|
+
children: /* @__PURE__ */ S("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
80
|
+
});
|
|
81
|
+
if (A) return /* @__PURE__ */ S("div", {
|
|
82
|
+
role: "alert",
|
|
83
|
+
className: "p-6 text-destructive",
|
|
84
|
+
children: t(A) ? "You don't have access to this meeting." : `Error loading meeting: ${A.message}`
|
|
85
|
+
});
|
|
86
|
+
let N = p?.planmagnetMeeting;
|
|
87
|
+
if (!N) return /* @__PURE__ */ S("div", {
|
|
88
|
+
className: "p-6 text-muted-foreground",
|
|
89
|
+
children: "Meeting not found."
|
|
90
|
+
});
|
|
91
|
+
let P = async (e, t) => {
|
|
92
|
+
await r(M({ variables: {
|
|
93
|
+
id: N.id,
|
|
94
|
+
input: { [e]: t || null }
|
|
95
|
+
} }));
|
|
96
|
+
};
|
|
97
|
+
return /* @__PURE__ */ C("div", {
|
|
98
|
+
className: "space-y-6 p-6",
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ C("div", {
|
|
101
|
+
className: "flex flex-wrap items-start justify-between gap-3",
|
|
102
|
+
children: [/* @__PURE__ */ C("div", { children: [
|
|
103
|
+
/* @__PURE__ */ C("div", {
|
|
104
|
+
className: "flex items-center gap-2",
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ S(m, { className: "h-5 w-5 text-primary" }),
|
|
107
|
+
/* @__PURE__ */ S("h1", {
|
|
108
|
+
className: "text-2xl font-semibold",
|
|
109
|
+
children: N.title
|
|
110
|
+
}),
|
|
111
|
+
/* @__PURE__ */ S(w, {
|
|
112
|
+
variant: "outline",
|
|
113
|
+
children: a(N.kind)
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ C("div", {
|
|
118
|
+
className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground",
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ C("span", { children: [
|
|
121
|
+
new Date(N.startsAt).toLocaleString(void 0, {
|
|
122
|
+
dateStyle: "medium",
|
|
123
|
+
timeStyle: "short"
|
|
124
|
+
}),
|
|
125
|
+
" – ",
|
|
126
|
+
new Date(N.endsAt).toLocaleString(void 0, { timeStyle: "short" })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ C("span", {
|
|
129
|
+
className: "inline-flex items-center gap-1",
|
|
130
|
+
children: [/* @__PURE__ */ S(_, { className: "h-3.5 w-3.5" }), c(N.recurrenceRule)]
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ C("a", {
|
|
133
|
+
href: `/calendar/event/${encodeURIComponent(N.calendarEventId)}`,
|
|
134
|
+
className: "inline-flex items-center gap-1 text-primary hover:underline",
|
|
135
|
+
"data-testid": "meeting-open-in-calendar",
|
|
136
|
+
children: ["Open in calendar", /* @__PURE__ */ S(h, { className: "h-3.5 w-3.5" })]
|
|
137
|
+
})
|
|
138
|
+
]
|
|
139
|
+
}),
|
|
140
|
+
/* @__PURE__ */ C("div", {
|
|
141
|
+
className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
|
|
142
|
+
children: [
|
|
143
|
+
N.project && /* @__PURE__ */ C("button", {
|
|
144
|
+
type: "button",
|
|
145
|
+
className: "inline-flex items-center gap-1 hover:underline",
|
|
146
|
+
onClick: () => i(o("/projects/project-detail-page", N.project.id)),
|
|
147
|
+
children: [/* @__PURE__ */ S(g, { className: "h-3 w-3" }), N.project.name]
|
|
148
|
+
}),
|
|
149
|
+
N.sprint && /* @__PURE__ */ C("button", {
|
|
150
|
+
type: "button",
|
|
151
|
+
className: "inline-flex items-center gap-1 hover:underline",
|
|
152
|
+
onClick: () => i(o("/projects/planning-detail-page", N.sprint.id)),
|
|
153
|
+
children: [/* @__PURE__ */ S(y, { className: "h-3 w-3" }), N.sprint.name]
|
|
154
|
+
}),
|
|
155
|
+
N.milestone && /* @__PURE__ */ C("button", {
|
|
156
|
+
type: "button",
|
|
157
|
+
className: "inline-flex items-center gap-1 hover:underline",
|
|
158
|
+
onClick: () => i(o("/projects/milestone-detail-page", N.milestone.id)),
|
|
159
|
+
children: [/* @__PURE__ */ S(v, { className: "h-3 w-3" }), N.milestone.name]
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
})
|
|
163
|
+
] }), /* @__PURE__ */ S(f, {
|
|
164
|
+
meetingId: N.id,
|
|
165
|
+
calendarEventId: N.calendarEventId,
|
|
166
|
+
meetingTitle: N.title,
|
|
167
|
+
onUnlinked: () => i("/meetings/meetings-page")
|
|
168
|
+
})]
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ C("div", {
|
|
171
|
+
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
172
|
+
children: [/* @__PURE__ */ C(E, { children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ S(k, { children: "Agenda" }) }), /* @__PURE__ */ S(D, { children: /* @__PURE__ */ S(j, {
|
|
173
|
+
id: "meeting-agenda",
|
|
174
|
+
label: "Agenda",
|
|
175
|
+
value: N.agenda,
|
|
176
|
+
placeholder: "What this meeting is for…",
|
|
177
|
+
onSave: (e) => P("agenda", e)
|
|
178
|
+
}) })] }), /* @__PURE__ */ C(E, { children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ S(k, { children: "Notes" }) }), /* @__PURE__ */ S(D, { children: /* @__PURE__ */ S(j, {
|
|
179
|
+
id: "meeting-notes",
|
|
180
|
+
label: "Notes",
|
|
181
|
+
value: N.notes,
|
|
182
|
+
placeholder: "What happened…",
|
|
183
|
+
onSave: (e) => P("notes", e)
|
|
184
|
+
}) })] })]
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ S(d, {
|
|
187
|
+
meetingId: N.id,
|
|
188
|
+
projectId: N.projectId,
|
|
189
|
+
actionItems: N.actionItems
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
export { M as MeetingsMeetingDetailPagePage };
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=MeetingsMeetingDetailPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingsMeetingDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/MeetingsMeetingDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, FolderKanban, Repeat, Target, Zap } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { mutationErrorMessage, isForbidden } from '../utils/graphqlErrors';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { summarizeRecurrence, type MeetingKind } from '../meetings/meetingModel';\nimport { GET_PLANMAGNET_MEETING, UPDATE_PLANMAGNET_MEETING } from '../meetings/meetingGraphql';\nimport { MeetingActionItemsPanel } from '../meetings/MeetingActionItemsPanel';\nimport { UnlinkMeetingDialog } from '../meetings/UnlinkMeetingDialog';\n\n/**\n * A single meeting's detail page (GAP-06, CWF-22): agenda + notes (PlanMagnet-owned content), its action items,\n * and Unlink. The schedule itself (start/end/recurrence) is shown read-only here — editing it is a calendar\n * concern (\"Open in calendar\"), not this page's (`PlanmagnetMeeting`'s doc comment: the calendar event stays\n * the source of truth for the real schedule).\n */\n\ninterface MeetingDetail {\n id: string;\n title: string;\n kind: MeetingKind;\n agenda: string | null;\n notes: string | null;\n startsAt: string;\n endsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n projectId: string;\n project: { id: string; name: string } | null;\n sprintId: string | null;\n sprint: { id: string; name: string } | null;\n milestoneId: string | null;\n milestone: { id: string; name: string } | null;\n actionItems: {\n id: string;\n workItemId: string;\n workItem: { id: string; key: string; title: string; status: string } | null;\n }[];\n}\n\n/** A plain-text field (agenda or notes) with its own dirty-tracked Save/Cancel — never rendered as HTML. */\nfunction EditableTextField({\n id,\n label,\n value,\n placeholder,\n onSave,\n}: {\n id: string;\n label: string;\n value: string | null;\n placeholder: string;\n onSave: (next: string) => Promise<void>;\n}) {\n const [draft, setDraft] = useState(value ?? '');\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const original = value ?? '';\n const dirty = draft !== original;\n\n const save = async () => {\n setError(null);\n setSaving(true);\n try {\n await onSave(draft.trim());\n } catch (e) {\n setError(mutationErrorMessage(e, `The ${label.toLowerCase()} could not be saved.`));\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"space-y-1\">\n <label htmlFor={id} className=\"text-sm font-medium\">\n {label}\n </label>\n <Textarea\n id={id}\n className=\"min-h-[120px]\"\n placeholder={placeholder}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n data-testid={id}\n />\n {error && <ErrorLine message={error} />}\n {dirty && (\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setDraft(original)} disabled={saving}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={saving}\n data-testid={`${id}-save`}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nexport function MeetingsMeetingDetailPagePage() {\n const meetingId = useEntityId('meetingId');\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<{ planmagnetMeeting: MeetingDetail | null }>(\n GET_PLANMAGNET_MEETING,\n { variables: { id: meetingId }, skip: !meetingId }\n );\n const [update] = useMutation(UPDATE_PLANMAGNET_MEETING, {\n refetchQueries: ['GetPlanmagnetMeeting'],\n awaitRefetchQueries: true,\n });\n\n if (!meetingId)\n return (\n <div className=\"p-6 text-muted-foreground\">No meeting selected. Open one from the list.</div>\n );\n // Only before the first load: a refetch after Save/Unlink must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return (\n <div role=\"alert\" className=\"p-6 text-destructive\">\n {isForbidden(error)\n ? \"You don't have access to this meeting.\"\n : `Error loading meeting: ${error.message}`}\n </div>\n );\n\n const meeting = data?.planmagnetMeeting;\n if (!meeting) return <div className=\"p-6 text-muted-foreground\">Meeting not found.</div>;\n\n const saveField = async (field: 'agenda' | 'notes', next: string) => {\n await throwOnError(update({ variables: { id: meeting.id, input: { [field]: next || null } } }));\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{meeting.title}</h1>\n <Badge variant=\"outline\">{enumLabel(meeting.kind)}</Badge>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground\">\n <span>\n {new Date(meeting.startsAt).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n {' – '}\n {new Date(meeting.endsAt).toLocaleString(undefined, { timeStyle: 'short' })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Repeat className=\"h-3.5 w-3.5\" />\n {summarizeRecurrence(meeting.recurrenceRule)}\n </span>\n <a\n href={`/calendar/event/${encodeURIComponent(meeting.calendarEventId)}`}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n data-testid=\"meeting-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3.5 w-3.5\" />\n </a>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n {meeting.project && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/project-detail-page', meeting.project!.id))\n }\n >\n <FolderKanban className=\"h-3 w-3\" />\n {meeting.project.name}\n </button>\n )}\n {meeting.sprint && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/planning-detail-page', meeting.sprint!.id))\n }\n >\n <Zap className=\"h-3 w-3\" />\n {meeting.sprint.name}\n </button>\n )}\n {meeting.milestone && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/milestone-detail-page', meeting.milestone!.id))\n }\n >\n <Target className=\"h-3 w-3\" />\n {meeting.milestone.name}\n </button>\n )}\n </div>\n </div>\n <UnlinkMeetingDialog\n meetingId={meeting.id}\n calendarEventId={meeting.calendarEventId}\n meetingTitle={meeting.title}\n onUnlinked={() => navigate('/meetings/meetings-page')}\n />\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Agenda</CardTitle>\n </CardHeader>\n <CardContent>\n <EditableTextField\n id=\"meeting-agenda\"\n label=\"Agenda\"\n value={meeting.agenda}\n placeholder=\"What this meeting is for…\"\n onSave={(next) => saveField('agenda', next)}\n />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Notes</CardTitle>\n </CardHeader>\n <CardContent>\n <EditableTextField\n id=\"meeting-notes\"\n label=\"Notes\"\n value={meeting.notes}\n placeholder=\"What happened…\"\n onSave={(next) => saveField('notes', next)}\n />\n </CardContent>\n </Card>\n </div>\n\n <MeetingActionItemsPanel\n meetingId={meeting.id}\n projectId={meeting.projectId}\n actionItems={meeting.actionItems}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingsMeetingsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/MeetingsMeetingsPagePage.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,4CAgMvC"}
|
|
@@ -0,0 +1,173 @@
|
|
|
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, detailPath as a, rowLinkProps as o, stopRowActivation as s } from "../utils/rowNavigation.js";
|
|
5
|
+
import { ProjectSelect as c } from "../controls/Pickers.js";
|
|
6
|
+
import { readUrlParam as l, writeUrlParam as u } from "../utils/urlParam.js";
|
|
7
|
+
import { MEETING_KINDS as d, summarizeRecurrence as f } from "../meetings/meetingModel.js";
|
|
8
|
+
import { LIST_PLANMAGNET_MEETINGS as p } from "../meetings/meetingGraphql.js";
|
|
9
|
+
import { ScheduleMeetingDialog as m } from "../meetings/ScheduleMeetingDialog.js";
|
|
10
|
+
import { useState as h } from "react";
|
|
11
|
+
import { CalendarClock as g, ExternalLink as _, ListChecks as v } from "lucide-react";
|
|
12
|
+
import { useQuery as y } from "@apollo/client/react";
|
|
13
|
+
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
14
|
+
import { Badge as S } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
//#region src/planmagnet/pages/MeetingsMeetingsPagePage.tsx
|
|
16
|
+
function C() {
|
|
17
|
+
let { workspaceId: C } = e(), w = t(), [T, E] = h(() => l("projectId") ?? ""), [D, O] = h(() => l("kind") ?? "all"), [k, A] = h(() => l("from") ?? ""), [j, M] = h(() => l("to") ?? ""), N = (e) => {
|
|
18
|
+
E(e), u("projectId", e || null);
|
|
19
|
+
}, P = (e) => {
|
|
20
|
+
O(e), u("kind", e === "all" ? null : e);
|
|
21
|
+
}, F = (e) => {
|
|
22
|
+
A(e), u("from", e || null);
|
|
23
|
+
}, I = (e) => {
|
|
24
|
+
M(e), u("to", e || null);
|
|
25
|
+
}, L = {};
|
|
26
|
+
T && (L.projectId = T), D !== "all" && (L.kind = D), k && (L.from = (/* @__PURE__ */ new Date(`${k}T00:00:00.000Z`)).toISOString()), j && (L.to = (/* @__PURE__ */ new Date(`${j}T23:59:59.999Z`)).toISOString());
|
|
27
|
+
let { data: R, loading: z, error: B } = y(p, {
|
|
28
|
+
variables: {
|
|
29
|
+
workspaceId: C ?? "default",
|
|
30
|
+
filter: L,
|
|
31
|
+
pagination: { first: 100 }
|
|
32
|
+
},
|
|
33
|
+
skip: !C
|
|
34
|
+
});
|
|
35
|
+
if (z && !R) return /* @__PURE__ */ b("div", {
|
|
36
|
+
className: "flex items-center justify-center p-12",
|
|
37
|
+
children: /* @__PURE__ */ b("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
38
|
+
});
|
|
39
|
+
let V = [...R?.planmagnetMeetings.items ?? []].sort((e, t) => e.startsAt.localeCompare(t.startsAt)), H = R?.planmagnetMeetings.totalCount ?? 0;
|
|
40
|
+
return /* @__PURE__ */ x("div", {
|
|
41
|
+
className: "space-y-6 p-6",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ x("div", {
|
|
44
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
45
|
+
children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ x("h1", {
|
|
46
|
+
className: "text-2xl font-semibold flex items-center gap-2",
|
|
47
|
+
children: [/* @__PURE__ */ b(g, { className: "h-5 w-5 text-primary" }), "Meetings"]
|
|
48
|
+
}), /* @__PURE__ */ x("p", {
|
|
49
|
+
className: "text-sm text-muted-foreground",
|
|
50
|
+
children: [
|
|
51
|
+
H,
|
|
52
|
+
" meeting",
|
|
53
|
+
H === 1 ? "" : "s",
|
|
54
|
+
T ? " for this project" : " across all projects"
|
|
55
|
+
]
|
|
56
|
+
})] }), T && /* @__PURE__ */ b(m, { projectId: T })]
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ x("div", {
|
|
59
|
+
className: "flex flex-wrap items-end gap-3",
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
|
|
62
|
+
htmlFor: "meetings-filter-project",
|
|
63
|
+
className: "text-xs text-muted-foreground",
|
|
64
|
+
children: "Project"
|
|
65
|
+
}), /* @__PURE__ */ b(c, {
|
|
66
|
+
id: "meetings-filter-project",
|
|
67
|
+
value: T,
|
|
68
|
+
onChange: N,
|
|
69
|
+
noneLabel: "All projects"
|
|
70
|
+
})] }),
|
|
71
|
+
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
|
|
72
|
+
htmlFor: "meetings-filter-kind",
|
|
73
|
+
className: "text-xs text-muted-foreground",
|
|
74
|
+
children: "Kind"
|
|
75
|
+
}), /* @__PURE__ */ x("select", {
|
|
76
|
+
id: "meetings-filter-kind",
|
|
77
|
+
className: "mt-1 rounded-md border border-input bg-background px-2 py-1.5 text-sm",
|
|
78
|
+
value: D,
|
|
79
|
+
onChange: (e) => P(e.target.value),
|
|
80
|
+
children: [/* @__PURE__ */ b("option", {
|
|
81
|
+
value: "all",
|
|
82
|
+
children: "All kinds"
|
|
83
|
+
}), d.map((e) => /* @__PURE__ */ b("option", {
|
|
84
|
+
value: e,
|
|
85
|
+
children: r(e)
|
|
86
|
+
}, e))]
|
|
87
|
+
})] }),
|
|
88
|
+
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
|
|
89
|
+
htmlFor: "meetings-filter-from",
|
|
90
|
+
className: "text-xs text-muted-foreground",
|
|
91
|
+
children: "From"
|
|
92
|
+
}), /* @__PURE__ */ b("input", {
|
|
93
|
+
id: "meetings-filter-from",
|
|
94
|
+
type: "date",
|
|
95
|
+
className: "mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm",
|
|
96
|
+
value: k,
|
|
97
|
+
onChange: (e) => F(e.target.value)
|
|
98
|
+
})] }),
|
|
99
|
+
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
|
|
100
|
+
htmlFor: "meetings-filter-to",
|
|
101
|
+
className: "text-xs text-muted-foreground",
|
|
102
|
+
children: "To"
|
|
103
|
+
}), /* @__PURE__ */ b("input", {
|
|
104
|
+
id: "meetings-filter-to",
|
|
105
|
+
type: "date",
|
|
106
|
+
className: "mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm",
|
|
107
|
+
value: j,
|
|
108
|
+
onChange: (e) => I(e.target.value)
|
|
109
|
+
})] })
|
|
110
|
+
]
|
|
111
|
+
}),
|
|
112
|
+
B && /* @__PURE__ */ b("div", {
|
|
113
|
+
role: "alert",
|
|
114
|
+
className: "rounded-md border border-destructive/40 p-4 text-sm text-destructive",
|
|
115
|
+
children: n(B) ? "You don't have access to these meetings." : `Meetings could not be loaded: ${B.message}`
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ x("div", {
|
|
118
|
+
className: "space-y-2",
|
|
119
|
+
children: [V.map((e) => /* @__PURE__ */ x("div", {
|
|
120
|
+
...o(() => w(a("/meetings/meeting-detail-page", e.id)), e.title),
|
|
121
|
+
className: `flex flex-wrap items-center justify-between gap-2 rounded-md border p-3 ${i}`,
|
|
122
|
+
"data-testid": "meetings-list-row",
|
|
123
|
+
children: [/* @__PURE__ */ x("div", {
|
|
124
|
+
className: "min-w-0 space-y-1",
|
|
125
|
+
children: [/* @__PURE__ */ x("div", {
|
|
126
|
+
className: "flex items-center gap-2",
|
|
127
|
+
children: [/* @__PURE__ */ b("span", {
|
|
128
|
+
className: "text-sm font-medium truncate",
|
|
129
|
+
children: e.title
|
|
130
|
+
}), /* @__PURE__ */ b(S, {
|
|
131
|
+
variant: "outline",
|
|
132
|
+
className: "text-xs shrink-0",
|
|
133
|
+
children: r(e.kind)
|
|
134
|
+
})]
|
|
135
|
+
}), /* @__PURE__ */ x("div", {
|
|
136
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ b("span", { children: new Date(e.startsAt).toLocaleString(void 0, {
|
|
139
|
+
dateStyle: "medium",
|
|
140
|
+
timeStyle: "short"
|
|
141
|
+
}) }),
|
|
142
|
+
/* @__PURE__ */ b("span", { children: e.project?.name ?? "Unknown project" }),
|
|
143
|
+
/* @__PURE__ */ b("span", { children: f(e.recurrenceRule) }),
|
|
144
|
+
e.actionItems.length > 0 && /* @__PURE__ */ x("span", {
|
|
145
|
+
className: "inline-flex items-center gap-1",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ b(v, { className: "h-3 w-3" }),
|
|
148
|
+
e.actionItems.length,
|
|
149
|
+
" action item",
|
|
150
|
+
e.actionItems.length === 1 ? "" : "s"
|
|
151
|
+
]
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
})]
|
|
155
|
+
}), /* @__PURE__ */ x("a", {
|
|
156
|
+
href: `/calendar/event/${encodeURIComponent(e.calendarEventId)}`,
|
|
157
|
+
onClick: s,
|
|
158
|
+
className: "inline-flex shrink-0 items-center gap-1 text-sm text-primary hover:underline",
|
|
159
|
+
"data-testid": "meetings-list-open-in-calendar",
|
|
160
|
+
children: ["Open in calendar", /* @__PURE__ */ b(_, { className: "h-3.5 w-3.5" })]
|
|
161
|
+
})]
|
|
162
|
+
}, e.id)), !B && V.length === 0 && /* @__PURE__ */ b("p", {
|
|
163
|
+
className: "text-center text-muted-foreground py-8",
|
|
164
|
+
children: T ? "No meetings match these filters." : "No meetings yet. Pick a project above to schedule one."
|
|
165
|
+
})]
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { C as MeetingsMeetingsPagePage };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=MeetingsMeetingsPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingsMeetingsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/MeetingsMeetingsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, ListChecks } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\nimport {\n ROW_LINK_CLASS,\n rowLinkProps,\n detailPath,\n stopRowActivation,\n} from '../utils/rowNavigation';\nimport { readUrlParam, writeUrlParam } from '../utils/urlParam';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { MEETING_KINDS, type MeetingKind, summarizeRecurrence } from '../meetings/meetingModel';\nimport { LIST_PLANMAGNET_MEETINGS } from '../meetings/meetingGraphql';\nimport { ScheduleMeetingDialog } from '../meetings/ScheduleMeetingDialog';\n\n/**\n * Every meeting in the workspace (GAP-06, CWF-22), filterable by project/kind/date. \"Schedule meeting\" needs a\n * fixed project (the dialog's own contract — see `ScheduleMeetingDialog`), so it only appears once a project\n * filter is chosen; otherwise picking one is the first step the empty state points at.\n */\n\ninterface MeetingRow {\n id: string;\n title: string;\n kind: MeetingKind;\n startsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n project: { id: string; name: string } | null;\n actionItems: { id: string }[];\n}\n\nexport function MeetingsMeetingsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [projectId, setProjectIdState] = useState(() => readUrlParam('projectId') ?? '');\n const [kind, setKindState] = useState(() => readUrlParam('kind') ?? 'all');\n const [from, setFromState] = useState(() => readUrlParam('from') ?? '');\n const [to, setToState] = useState(() => readUrlParam('to') ?? '');\n\n const setProjectId = (next: string) => {\n setProjectIdState(next);\n writeUrlParam('projectId', next || null);\n };\n const setKind = (next: string) => {\n setKindState(next);\n writeUrlParam('kind', next === 'all' ? null : next);\n };\n const setFrom = (next: string) => {\n setFromState(next);\n writeUrlParam('from', next || null);\n };\n const setTo = (next: string) => {\n setToState(next);\n writeUrlParam('to', next || null);\n };\n\n const filter: Record<string, unknown> = {};\n if (projectId) filter.projectId = projectId;\n if (kind !== 'all') filter.kind = kind;\n // A bare `from` is UTC midnight; `to` is pushed to the end of that day so picking one day as both bounds\n // still matches meetings that start later that same day (same convention as ActivityActivityPagePage).\n if (from) filter.from = new Date(`${from}T00:00:00.000Z`).toISOString();\n if (to) filter.to = new Date(`${to}T23:59:59.999Z`).toISOString();\n\n const { data, loading, error } = useQuery<{\n planmagnetMeetings: { items: MeetingRow[]; totalCount: number };\n }>(LIST_PLANMAGNET_MEETINGS, {\n variables: { workspaceId: workspaceId ?? 'default', filter, pagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch after scheduling 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\n const meetings = [...(data?.planmagnetMeetings.items ?? [])].sort((a, b) =>\n a.startsAt.localeCompare(b.startsAt)\n );\n const totalCount = data?.planmagnetMeetings.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <CalendarClock className=\"h-5 w-5 text-primary\" />\n Meetings\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {totalCount} meeting{totalCount === 1 ? '' : 's'}\n {projectId ? ' for this project' : ' across all projects'}\n </p>\n </div>\n {projectId && <ScheduleMeetingDialog projectId={projectId} />}\n </div>\n\n <div className=\"flex flex-wrap items-end gap-3\">\n <div>\n <label htmlFor=\"meetings-filter-project\" className=\"text-xs text-muted-foreground\">\n Project\n </label>\n <ProjectSelect\n id=\"meetings-filter-project\"\n value={projectId}\n onChange={setProjectId}\n noneLabel=\"All projects\"\n />\n </div>\n <div>\n <label htmlFor=\"meetings-filter-kind\" className=\"text-xs text-muted-foreground\">\n Kind\n </label>\n <select\n id=\"meetings-filter-kind\"\n className=\"mt-1 rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={kind}\n onChange={(e) => setKind(e.target.value)}\n >\n <option value=\"all\">All kinds</option>\n {MEETING_KINDS.map((k) => (\n <option key={k} value={k}>\n {enumLabel(k)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"meetings-filter-from\" className=\"text-xs text-muted-foreground\">\n From\n </label>\n <input\n id=\"meetings-filter-from\"\n type=\"date\"\n className=\"mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={from}\n onChange={(e) => setFrom(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor=\"meetings-filter-to\" className=\"text-xs text-muted-foreground\">\n To\n </label>\n <input\n id=\"meetings-filter-to\"\n type=\"date\"\n className=\"mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={to}\n onChange={(e) => setTo(e.target.value)}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 p-4 text-sm text-destructive\"\n >\n {isForbidden(error)\n ? \"You don't have access to these meetings.\"\n : `Meetings could not be loaded: ${error.message}`}\n </div>\n )}\n\n <div className=\"space-y-2\">\n {meetings.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={`flex flex-wrap items-center justify-between gap-2 rounded-md border p-3 ${ROW_LINK_CLASS}`}\n data-testid=\"meetings-list-row\"\n >\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex items-center 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>{m.project?.name ?? 'Unknown project'}</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 </div>\n </div>\n <a\n href={`/calendar/event/${encodeURIComponent(m.calendarEventId)}`}\n onClick={stopRowActivation}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm text-primary hover:underline\"\n data-testid=\"meetings-list-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3.5 w-3.5\" />\n </a>\n </div>\n ))}\n {!error && meetings.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {projectId\n ? 'No meetings match these filters.'\n : 'No meetings yet. Pick a project above to schedule one.'}\n </p>\n )}\n </div>\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":"
|
|
1
|
+
{"version":3,"file":"ProjectsMilestonesPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsMilestonesPagePage.tsx"],"names":[],"mappings":"AA6DA,wBAAgB,0BAA0B,4CA6JzC"}
|