@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.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +163 -150
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +124 -108
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/comments/CommentThread.js +36 -36
- package/dist/planmagnet/comments/RoadmapComments.js +21 -21
- package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
- package/dist/planmagnet/controls/FeatureControls.js +60 -60
- package/dist/planmagnet/controls/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/controls/SyncControls.js +59 -59
- package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
- package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
- package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.js +37 -0
- package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
- package/dist/planmagnet/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/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/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/ProjectsAtRiskPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
- package/dist/planmagnet/pages/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 +211 -182
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
- package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
- package/dist/planmagnet/product/ProductControls.js +91 -91
- package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
- package/dist/planmagnet/reports/ShareDialog.js +76 -76
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
- package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
- package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
- package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScheduleCeremoniesDialog.js","names":[],"sources":["../../../src/planmagnet/meetings/ScheduleCeremoniesDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarRange, CheckCircle2, XCircle } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport {\n type MeetingKind,\n DURATION_OPTIONS_MINUTES,\n localInputValueToISO,\n addMinutesIso,\n viewerTimeZone,\n rruleUntil,\n} from './meetingModel';\nimport {\n SCHEDULE_MEETING_CALENDARS,\n CREATE_CALENDAR_EVENT,\n DELETE_CALENDAR_EVENT,\n LINK_PLANMAGNET_MEETING,\n MEETING_QUERIES,\n} from './meetingGraphql';\nimport { createAndLinkMeeting, meetingScheduleErrorMessage } from './meetingActions';\n\n/**\n * CWF-23: propose a sprint's standard ceremonies from its start/end dates (Planning at start, a weekday Daily\n * standup for the sprint's length, Review and Retrospective at the end), let the times be edited, then create\n * them one at a time — reporting each one's own success or failure, since a partial result (three ceremonies\n * scheduled, one refused) is a real possible outcome, never collapsed into one pass/fail.\n */\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n/** A sprint's planned start/end are UTC-midnight calendar days (`SprintDates.tsx`'s convention) — combine the\n * day as-is with a local time-of-day rather than converting the UTC instant to local, which could shift the\n * calendar day itself near a time-zone boundary. */\nfunction combineDayAndTime(dayIso: string, hhmm: string): string {\n return `${dayIso.slice(0, 10)}T${hhmm}`;\n}\n\ninterface CeremonyDraft {\n key: 'PLANNING' | 'STANDUP' | 'REVIEW' | 'RETROSPECTIVE';\n label: string;\n kind: MeetingKind;\n startLocal: string;\n durationMinutes: number;\n /** Only the standup repeats — every weekday through the sprint's last day. */\n recurs: boolean;\n}\n\nfunction defaultCeremonies(startDate: string, endDate: string): CeremonyDraft[] {\n return [\n {\n key: 'PLANNING',\n label: 'Planning',\n kind: 'PLANNING',\n startLocal: combineDayAndTime(startDate, '10:00'),\n durationMinutes: 60,\n recurs: false,\n },\n {\n key: 'STANDUP',\n label: 'Daily standup',\n kind: 'STANDUP',\n startLocal: combineDayAndTime(startDate, '09:30'),\n durationMinutes: 15,\n recurs: true,\n },\n {\n key: 'REVIEW',\n label: 'Review',\n kind: 'REVIEW',\n startLocal: combineDayAndTime(endDate, '14:00'),\n durationMinutes: 60,\n recurs: false,\n },\n {\n key: 'RETROSPECTIVE',\n label: 'Retrospective',\n kind: 'RETROSPECTIVE',\n startLocal: combineDayAndTime(endDate, '15:15'),\n durationMinutes: 45,\n recurs: false,\n },\n ];\n}\n\ninterface CalendarOption {\n id: string;\n name: string;\n isDefault: boolean;\n}\n\ntype CeremonyResult = {\n key: CeremonyDraft['key'];\n label: string;\n} & ({ status: 'success'; meetingId: string } | { status: 'error'; message: string });\n\nexport function ScheduleCeremoniesDialog({\n projectId,\n sprintId,\n startDate,\n endDate,\n}: {\n projectId: string;\n sprintId: string;\n /** Planned start/end (UTC-midnight calendar days). Null disables the whole flow — a sprint with no dates has\n * nothing to propose against. */\n startDate: string | null;\n endDate: string | null;\n}) {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [open, setOpen] = useState(false);\n const [calendarId, setCalendarId] = useState('');\n const [ceremonies, setCeremonies] = useState<CeremonyDraft[]>([]);\n const [submitting, setSubmitting] = useState(false);\n const [results, setResults] = useState<CeremonyResult[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const { data: calendarsData, loading: calendarsLoading } = useQuery<{\n calendars: { calendars: CalendarOption[] };\n }>(SCHEDULE_MEETING_CALENDARS, { variables: { pagination: { limit: 100 } }, skip: !open });\n const calendars = calendarsData?.calendars.calendars ?? [];\n\n const [createEvent] = useMutation<{ createEvent: { id: string } }>(CREATE_CALENDAR_EVENT);\n const [linkMeeting] = useMutation<{ planmagnetLinkMeeting: { id: string } }>(\n LINK_PLANMAGNET_MEETING,\n { refetchQueries: MEETING_QUERIES, awaitRefetchQueries: true }\n );\n const [deleteEvent] = useMutation<{ deleteEvent: boolean }>(DELETE_CALENDAR_EVENT);\n\n const canSchedule = Boolean(startDate && endDate);\n\n const openDialog = (next: boolean) => {\n setOpen(next);\n setError(null);\n setResults(null);\n setSubmitting(false);\n if (next && startDate && endDate) {\n setCeremonies(defaultCeremonies(startDate, endDate));\n setCalendarId('');\n }\n };\n\n const updateCeremony = (key: CeremonyDraft['key'], patch: Partial<CeremonyDraft>) =>\n setCeremonies((prev) => prev.map((c) => (c.key === key ? { ...c, ...patch } : c)));\n\n const scheduleAll = async () => {\n if (!workspaceId || !calendarId || !startDate || !endDate) {\n setError('Pick a calendar first.');\n return;\n }\n setError(null);\n setSubmitting(true);\n const outcomes: CeremonyResult[] = [];\n // One at a time (not Promise.all): a partial result must still report which ceremonies actually succeeded,\n // and a later failure must never be mistaken for an earlier one.\n for (const c of ceremonies) {\n const startIso = localInputValueToISO(c.startLocal);\n if (!startIso) {\n outcomes.push({\n key: c.key,\n label: c.label,\n status: 'error',\n message: 'Invalid start time.',\n });\n continue;\n }\n const endIso = addMinutesIso(startIso, c.durationMinutes);\n // The standup's series runs through the sprint's own last day — end of that UTC calendar day, so the\n // final standup still falls on the sprint's actual end date regardless of the viewer's own time zone.\n const until = c.recurs ? new Date(`${endDate.slice(0, 10)}T23:59:59.000Z`) : null;\n const rrule = c.recurs\n ? `FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;UNTIL=${rruleUntil(until!)}`\n : undefined;\n try {\n const meeting = await createAndLinkMeeting({\n createEvent,\n linkMeeting,\n deleteEvent,\n workspaceId,\n calendarId,\n title: c.label,\n startIso,\n endIso,\n kind: c.kind,\n projectId,\n sprintId,\n agenda: '',\n rrule,\n timezone: viewerTimeZone(),\n });\n outcomes.push({ key: c.key, label: c.label, status: 'success', meetingId: meeting.id });\n } catch (e) {\n outcomes.push({\n key: c.key,\n label: c.label,\n status: 'error',\n message: meetingScheduleErrorMessage(e),\n });\n }\n }\n setResults(outcomes);\n setSubmitting(false);\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={openDialog}>\n <div className=\"flex flex-col items-end gap-1\">\n {/* Not wrapped together with the explanation text in one `asChild` child: that would make clicking the\n text (outside the disabled button but inside a shared wrapper) still fire the Radix trigger. A plain\n disabled <button> never dispatches a click at all, so keeping it as the trigger's one child is what\n actually blocks opening the dialog — the explanation stays a separate sibling. */}\n <ResponsiveDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={!canSchedule}\n data-testid=\"schedule-ceremonies-trigger\"\n >\n <CalendarRange className=\"mr-1 h-4 w-4\" />\n Schedule ceremonies\n </Button>\n </ResponsiveDialogTrigger>\n {!canSchedule && (\n <span className=\"text-xs text-muted-foreground text-right max-w-[14rem]\">\n Set this sprint's start and end dates to propose its ceremonies.\n </span>\n )}\n </div>\n <ResponsiveDialogContent className=\"max-w-xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Schedule ceremonies</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Planning at the sprint's start, a weekday daily standup through its length, and\n Review + Retrospective at its end. Edit any time before scheduling.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n {results ? (\n <div className=\"space-y-2\" data-testid=\"schedule-ceremonies-results\">\n {results.map((r) => (\n <div\n key={r.key}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2 text-sm\"\n data-testid=\"schedule-ceremonies-result-row\"\n >\n <span className=\"flex items-center gap-2\">\n {r.status === 'success' ? (\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"h-4 w-4 text-destructive\" />\n )}\n {r.label}\n </span>\n {r.status === 'success' ? (\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => {\n setOpen(false);\n navigate(detailPath('/meetings/meeting-detail-page', r.meetingId));\n }}\n >\n View\n </button>\n ) : (\n <span className=\"text-destructive text-xs\">{r.message}</span>\n )}\n </div>\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"ceremonies-calendar\" className=\"text-sm font-medium\">\n Calendar\n </label>\n <select\n id=\"ceremonies-calendar\"\n className={fieldClass}\n value={calendarId}\n disabled={calendarsLoading}\n onChange={(e) => setCalendarId(e.target.value)}\n data-testid=\"ceremonies-calendar\"\n >\n <option value=\"\">\n {calendarsLoading ? 'Loading calendars…' : 'Choose a calendar'}\n </option>\n {calendars.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n {c.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n </div>\n {ceremonies.map((c) => (\n <div key={c.key} className=\"grid grid-cols-[1fr_auto_auto] items-end gap-2\">\n <div>\n <label\n htmlFor={`ceremony-start-${c.key}`}\n className=\"text-xs text-muted-foreground\"\n >\n {c.label}\n {c.recurs ? ' (weekdays, first occurrence)' : ''}\n </label>\n <Input\n id={`ceremony-start-${c.key}`}\n type=\"datetime-local\"\n className=\"mt-1\"\n value={c.startLocal}\n onChange={(e) => updateCeremony(c.key, { startLocal: e.target.value })}\n data-testid={`ceremony-start-${c.key}`}\n />\n </div>\n <div>\n <label\n htmlFor={`ceremony-duration-${c.key}`}\n className=\"text-xs text-muted-foreground\"\n >\n Duration\n </label>\n <select\n id={`ceremony-duration-${c.key}`}\n className={fieldClass}\n value={c.durationMinutes}\n onChange={(e) =>\n updateCeremony(c.key, { durationMinutes: Number(e.target.value) })\n }\n data-testid={`ceremony-duration-${c.key}`}\n >\n {DURATION_OPTIONS_MINUTES.map((m) => (\n <option key={m} value={m}>\n {m}m\n </option>\n ))}\n </select>\n </div>\n </div>\n ))}\n {error && <ErrorLine message={error} />}\n </div>\n )}\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n {results ? 'Close' : 'Cancel'}\n </Button>\n {!results && (\n <Button\n onClick={() => void scheduleAll()}\n disabled={submitting || !calendarId}\n data-testid=\"schedule-ceremonies-submit\"\n >\n {submitting ? 'Scheduling…' : `Schedule ${ceremonies.length} ceremonies`}\n </Button>\n )}\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { MeetingKind } from './meetingModel';
|
|
3
|
+
|
|
4
|
+
export declare function ScheduleMeetingDialog({ projectId, sprintId, milestoneId, defaultKind, lockKind, defaultTitle, trigger, triggerLabel, onScheduled, }: {
|
|
5
|
+
projectId: string;
|
|
6
|
+
sprintId?: string | null;
|
|
7
|
+
milestoneId?: string | null;
|
|
8
|
+
/** Pre-selects a kind — e.g. MILESTONE_CELEBRATION/DEMO from a milestone's own page. */
|
|
9
|
+
defaultKind?: MeetingKind;
|
|
10
|
+
/** Hides the kind picker, for a trigger whose kind the context already decided (Schedule celebration/demo). */
|
|
11
|
+
lockKind?: boolean;
|
|
12
|
+
defaultTitle?: string;
|
|
13
|
+
trigger?: ReactNode;
|
|
14
|
+
triggerLabel?: string;
|
|
15
|
+
onScheduled?: (meeting: {
|
|
16
|
+
id: string;
|
|
17
|
+
}) => void;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=ScheduleMeetingDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScheduleMeetingDialog.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/ScheduleMeetingDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAkBjD,OAAO,EAEL,KAAK,WAAW,EAWjB,MAAM,gBAAgB,CAAC;AA0BxB,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAe,EACf,WAAkB,EAClB,WAAqB,EACrB,QAAgB,EAChB,YAAiB,EACjB,OAAO,EACP,YAAiC,EACjC,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wFAAwF;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+GAA+G;IAC/G,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjD,2CAmRA"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { ErrorLine as t } from "../controls/ConfirmButton.js";
|
|
3
|
+
import { enumLabel as n } from "../controls/WorkItemControls.js";
|
|
4
|
+
import { DURATION_OPTIONS_MINUTES as r, MEETING_KINDS as ee, RECURRENCE_LABELS as i, RECURRENCE_OPTIONS as a, addMinutesIso as o, buildRRule as te, defaultLocalStart as s, localInputValueToISO as ne, validateMeetingDraft as re, viewerTimeZone as ie } from "./meetingModel.js";
|
|
5
|
+
import { CREATE_CALENDAR_EVENT as ae, DELETE_CALENDAR_EVENT as oe, LINK_PLANMAGNET_MEETING as se, MEETING_QUERIES as ce, SCHEDULE_MEETING_CALENDARS as c } from "./meetingGraphql.js";
|
|
6
|
+
import { createAndLinkMeeting as l, meetingScheduleErrorMessage as u } from "./meetingActions.js";
|
|
7
|
+
import { useState as d } from "react";
|
|
8
|
+
import { CalendarPlus as f } from "lucide-react";
|
|
9
|
+
import { useMutation as p, useQuery as m } from "@apollo/client/react";
|
|
10
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
11
|
+
import { Button as _, Input as v, ResponsiveDialog as le, ResponsiveDialogContent as ue, ResponsiveDialogDescription as de, ResponsiveDialogFooter as fe, ResponsiveDialogHeader as pe, ResponsiveDialogTitle as me, ResponsiveDialogTrigger as he, Textarea as ge } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/meetings/ScheduleMeetingDialog.tsx
|
|
13
|
+
var y = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60";
|
|
14
|
+
function b({ projectId: b, sprintId: x = null, milestoneId: S = null, defaultKind: C = "OTHER", lockKind: _e = !1, defaultTitle: w = "", trigger: ve, triggerLabel: T = "Schedule meeting", onScheduled: E }) {
|
|
15
|
+
let { workspaceId: D } = e(), [O, k] = d(!1), [A, j] = d(w), [M, N] = d(C), [P, F] = d(""), [I, L] = d(() => s()), [R, z] = d(30), [B, V] = d("NONE"), [H, U] = d(""), [W, G] = d(null), [K, q] = d(!1), { data: ye, loading: J } = m(c, {
|
|
16
|
+
variables: { pagination: { limit: 100 } },
|
|
17
|
+
skip: !O
|
|
18
|
+
}), Y = ye?.calendars.calendars ?? [], X = (Y.find((e) => e.isDefault) ?? Y[0])?.id ?? "", Z = P || X, [be] = p(ae), [xe] = p(se, {
|
|
19
|
+
refetchQueries: ce,
|
|
20
|
+
awaitRefetchQueries: !0
|
|
21
|
+
}), [Se] = p(oe), Q = () => {
|
|
22
|
+
j(w), N(C), F(""), L(s()), z(30), V("NONE"), U(""), G(null);
|
|
23
|
+
}, Ce = async () => {
|
|
24
|
+
let e = re({
|
|
25
|
+
title: A,
|
|
26
|
+
calendarId: Z,
|
|
27
|
+
startLocal: I,
|
|
28
|
+
durationMinutes: R,
|
|
29
|
+
kind: M,
|
|
30
|
+
milestoneId: S
|
|
31
|
+
});
|
|
32
|
+
if (e) {
|
|
33
|
+
G(e);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let t = ne(I);
|
|
37
|
+
if (!t || !D) {
|
|
38
|
+
G("Pick a start time.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
G(null), q(!0);
|
|
42
|
+
try {
|
|
43
|
+
let e = o(t, R), n = te(B, I), r = await l({
|
|
44
|
+
createEvent: be,
|
|
45
|
+
linkMeeting: xe,
|
|
46
|
+
deleteEvent: Se,
|
|
47
|
+
workspaceId: D,
|
|
48
|
+
calendarId: Z,
|
|
49
|
+
title: A.trim(),
|
|
50
|
+
startIso: t,
|
|
51
|
+
endIso: e,
|
|
52
|
+
kind: M,
|
|
53
|
+
projectId: b,
|
|
54
|
+
sprintId: x,
|
|
55
|
+
milestoneId: S,
|
|
56
|
+
agenda: H.trim(),
|
|
57
|
+
rrule: n,
|
|
58
|
+
timezone: ie()
|
|
59
|
+
});
|
|
60
|
+
k(!1), Q(), E?.(r);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
G(u(e));
|
|
63
|
+
} finally {
|
|
64
|
+
q(!1);
|
|
65
|
+
}
|
|
66
|
+
}, $ = O && !J && Y.length === 0;
|
|
67
|
+
return /* @__PURE__ */ g(le, {
|
|
68
|
+
open: O,
|
|
69
|
+
onOpenChange: (e) => {
|
|
70
|
+
k(e), e ? Q() : G(null);
|
|
71
|
+
},
|
|
72
|
+
children: [/* @__PURE__ */ h(he, {
|
|
73
|
+
asChild: !0,
|
|
74
|
+
children: ve ?? /* @__PURE__ */ g(_, {
|
|
75
|
+
size: "sm",
|
|
76
|
+
variant: "outline",
|
|
77
|
+
"data-testid": "schedule-meeting-trigger",
|
|
78
|
+
children: [/* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }), T]
|
|
79
|
+
})
|
|
80
|
+
}), /* @__PURE__ */ g(ue, {
|
|
81
|
+
className: "max-w-lg",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ g(pe, { children: [/* @__PURE__ */ h(me, { children: "Schedule a meeting" }), /* @__PURE__ */ g(de, { children: [
|
|
84
|
+
"Creates a calendar event and links it to this ",
|
|
85
|
+
S ? "milestone" : x ? "sprint" : "project",
|
|
86
|
+
"."
|
|
87
|
+
] })] }),
|
|
88
|
+
$ ? /* @__PURE__ */ g("div", {
|
|
89
|
+
className: "space-y-2 text-sm",
|
|
90
|
+
"data-testid": "schedule-meeting-no-calendars",
|
|
91
|
+
children: [/* @__PURE__ */ h("p", {
|
|
92
|
+
className: "text-muted-foreground",
|
|
93
|
+
children: "You have no calendars yet. Create one in Calendar settings, then come back to schedule this meeting."
|
|
94
|
+
}), /* @__PURE__ */ h("a", {
|
|
95
|
+
href: "/calendar/settings",
|
|
96
|
+
className: "text-primary hover:underline",
|
|
97
|
+
"data-testid": "schedule-meeting-calendar-settings-link",
|
|
98
|
+
children: "Open Calendar settings"
|
|
99
|
+
})]
|
|
100
|
+
}) : /* @__PURE__ */ g("div", {
|
|
101
|
+
className: "space-y-3",
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
104
|
+
htmlFor: "schedule-meeting-title",
|
|
105
|
+
className: "text-sm font-medium",
|
|
106
|
+
children: "Title"
|
|
107
|
+
}), /* @__PURE__ */ h(v, {
|
|
108
|
+
id: "schedule-meeting-title",
|
|
109
|
+
className: "mt-1",
|
|
110
|
+
value: A,
|
|
111
|
+
onChange: (e) => j(e.target.value),
|
|
112
|
+
"data-testid": "schedule-meeting-title"
|
|
113
|
+
})] }),
|
|
114
|
+
/* @__PURE__ */ g("div", {
|
|
115
|
+
className: "grid grid-cols-2 gap-3",
|
|
116
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
117
|
+
htmlFor: "schedule-meeting-kind",
|
|
118
|
+
className: "text-sm font-medium",
|
|
119
|
+
children: "Kind"
|
|
120
|
+
}), /* @__PURE__ */ h("select", {
|
|
121
|
+
id: "schedule-meeting-kind",
|
|
122
|
+
className: y,
|
|
123
|
+
value: M,
|
|
124
|
+
disabled: _e,
|
|
125
|
+
onChange: (e) => N(e.target.value),
|
|
126
|
+
"data-testid": "schedule-meeting-kind",
|
|
127
|
+
children: ee.map((e) => /* @__PURE__ */ h("option", {
|
|
128
|
+
value: e,
|
|
129
|
+
children: n(e)
|
|
130
|
+
}, e))
|
|
131
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
132
|
+
htmlFor: "schedule-meeting-calendar",
|
|
133
|
+
className: "text-sm font-medium",
|
|
134
|
+
children: "Calendar"
|
|
135
|
+
}), /* @__PURE__ */ g("select", {
|
|
136
|
+
id: "schedule-meeting-calendar",
|
|
137
|
+
className: y,
|
|
138
|
+
value: Z,
|
|
139
|
+
disabled: J,
|
|
140
|
+
onChange: (e) => F(e.target.value),
|
|
141
|
+
"data-testid": "schedule-meeting-calendar",
|
|
142
|
+
children: [J && /* @__PURE__ */ h("option", {
|
|
143
|
+
value: "",
|
|
144
|
+
children: "Loading calendars…"
|
|
145
|
+
}), Y.map((e) => /* @__PURE__ */ g("option", {
|
|
146
|
+
value: e.id,
|
|
147
|
+
children: [e.name, e.isDefault ? " (default)" : ""]
|
|
148
|
+
}, e.id))]
|
|
149
|
+
})] })]
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ g("div", {
|
|
152
|
+
className: "grid grid-cols-2 gap-3",
|
|
153
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
154
|
+
htmlFor: "schedule-meeting-start",
|
|
155
|
+
className: "text-sm font-medium",
|
|
156
|
+
children: "Start"
|
|
157
|
+
}), /* @__PURE__ */ h(v, {
|
|
158
|
+
id: "schedule-meeting-start",
|
|
159
|
+
type: "datetime-local",
|
|
160
|
+
className: "mt-1",
|
|
161
|
+
value: I,
|
|
162
|
+
onChange: (e) => L(e.target.value),
|
|
163
|
+
"data-testid": "schedule-meeting-start"
|
|
164
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
165
|
+
htmlFor: "schedule-meeting-duration",
|
|
166
|
+
className: "text-sm font-medium",
|
|
167
|
+
children: "Duration"
|
|
168
|
+
}), /* @__PURE__ */ h("select", {
|
|
169
|
+
id: "schedule-meeting-duration",
|
|
170
|
+
className: y,
|
|
171
|
+
value: R,
|
|
172
|
+
onChange: (e) => z(Number(e.target.value)),
|
|
173
|
+
"data-testid": "schedule-meeting-duration",
|
|
174
|
+
children: r.map((e) => /* @__PURE__ */ h("option", {
|
|
175
|
+
value: e,
|
|
176
|
+
children: e < 60 ? `${e} minutes` : `${e / 60} hour${e === 60 ? "" : "s"}`
|
|
177
|
+
}, e))
|
|
178
|
+
})] })]
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
181
|
+
htmlFor: "schedule-meeting-recurrence",
|
|
182
|
+
className: "text-sm font-medium",
|
|
183
|
+
children: "Repeats"
|
|
184
|
+
}), /* @__PURE__ */ h("select", {
|
|
185
|
+
id: "schedule-meeting-recurrence",
|
|
186
|
+
className: y,
|
|
187
|
+
value: B,
|
|
188
|
+
onChange: (e) => V(e.target.value),
|
|
189
|
+
"data-testid": "schedule-meeting-recurrence",
|
|
190
|
+
children: a.map((e) => /* @__PURE__ */ h("option", {
|
|
191
|
+
value: e,
|
|
192
|
+
children: i[e]
|
|
193
|
+
}, e))
|
|
194
|
+
})] }),
|
|
195
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
196
|
+
htmlFor: "schedule-meeting-agenda",
|
|
197
|
+
className: "text-sm font-medium",
|
|
198
|
+
children: "Agenda (optional)"
|
|
199
|
+
}), /* @__PURE__ */ h(ge, {
|
|
200
|
+
id: "schedule-meeting-agenda",
|
|
201
|
+
className: "mt-1 min-h-[72px]",
|
|
202
|
+
value: H,
|
|
203
|
+
onChange: (e) => U(e.target.value),
|
|
204
|
+
"data-testid": "schedule-meeting-agenda"
|
|
205
|
+
})] }),
|
|
206
|
+
W && /* @__PURE__ */ h(t, { message: W })
|
|
207
|
+
]
|
|
208
|
+
}),
|
|
209
|
+
/* @__PURE__ */ g(fe, { children: [/* @__PURE__ */ h(_, {
|
|
210
|
+
variant: "outline",
|
|
211
|
+
onClick: () => k(!1),
|
|
212
|
+
children: "Cancel"
|
|
213
|
+
}), !$ && /* @__PURE__ */ h(_, {
|
|
214
|
+
onClick: () => void Ce(),
|
|
215
|
+
disabled: K,
|
|
216
|
+
"data-testid": "schedule-meeting-submit",
|
|
217
|
+
children: K ? "Scheduling…" : "Schedule meeting"
|
|
218
|
+
})] })
|
|
219
|
+
]
|
|
220
|
+
})]
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { b as ScheduleMeetingDialog };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=ScheduleMeetingDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScheduleMeetingDialog.js","names":[],"sources":["../../../src/planmagnet/meetings/ScheduleMeetingDialog.tsx"],"sourcesContent":["import { useState, type ReactNode } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Input,\n Textarea,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarPlus } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport {\n MEETING_KINDS,\n type MeetingKind,\n RECURRENCE_OPTIONS,\n RECURRENCE_LABELS,\n type RecurrenceOption,\n DURATION_OPTIONS_MINUTES,\n localInputValueToISO,\n addMinutesIso,\n defaultLocalStart,\n viewerTimeZone,\n buildRRule,\n validateMeetingDraft,\n} from './meetingModel';\nimport {\n SCHEDULE_MEETING_CALENDARS,\n CREATE_CALENDAR_EVENT,\n DELETE_CALENDAR_EVENT,\n LINK_PLANMAGNET_MEETING,\n MEETING_QUERIES,\n} from './meetingGraphql';\nimport { createAndLinkMeeting, meetingScheduleErrorMessage } from './meetingActions';\n\n/**\n * Reusable \"schedule a meeting\" dialog (GAP-06, CWF-22). Opened with a FIXED project and an optional sprint or\n * milestone — see `createAndLinkMeeting` for the create-event-then-link flow, including what happens when the\n * link is refused after the calendar event was already created.\n */\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\ninterface CalendarOption {\n id: string;\n name: string;\n isDefault: boolean;\n calendarType: string;\n}\n\nexport function ScheduleMeetingDialog({\n projectId,\n sprintId = null,\n milestoneId = null,\n defaultKind = 'OTHER',\n lockKind = false,\n defaultTitle = '',\n trigger,\n triggerLabel = 'Schedule meeting',\n onScheduled,\n}: {\n projectId: string;\n sprintId?: string | null;\n milestoneId?: string | null;\n /** Pre-selects a kind — e.g. MILESTONE_CELEBRATION/DEMO from a milestone's own page. */\n defaultKind?: MeetingKind;\n /** Hides the kind picker, for a trigger whose kind the context already decided (Schedule celebration/demo). */\n lockKind?: boolean;\n defaultTitle?: string;\n trigger?: ReactNode;\n triggerLabel?: string;\n onScheduled?: (meeting: { id: string }) => void;\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [title, setTitle] = useState(defaultTitle);\n const [kind, setKind] = useState<MeetingKind>(defaultKind);\n const [calendarId, setCalendarId] = useState('');\n const [startLocal, setStartLocal] = useState<string>(() => defaultLocalStart());\n const [duration, setDuration] = useState<number>(30);\n const [recurrence, setRecurrence] = useState<RecurrenceOption>('NONE');\n const [agenda, setAgenda] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [submitting, setSubmitting] = useState(false);\n\n const { data: calendarsData, loading: calendarsLoading } = useQuery<{\n calendars: { calendars: CalendarOption[]; total: number };\n }>(SCHEDULE_MEETING_CALENDARS, {\n variables: { pagination: { limit: 100 } },\n skip: !open,\n });\n const calendars = calendarsData?.calendars.calendars ?? [];\n // The workspace default calendar, derived rather than synced through an effect: `calendarId` only ever holds\n // an EXPLICIT user choice (empty until they pick one), and this fills in the default for both the `<select>`\n // and submission until they do — so the default appears the moment calendars arrive, with no separate\n // render pass, and never fights a choice the user already made.\n const defaultCalendarId = (calendars.find((c) => c.isDefault) ?? calendars[0])?.id ?? '';\n const effectiveCalendarId = calendarId || defaultCalendarId;\n\n const [createEvent] = useMutation<{ createEvent: { id: string } }>(CREATE_CALENDAR_EVENT);\n const [linkMeeting] = useMutation<{ planmagnetLinkMeeting: { id: string } }>(\n LINK_PLANMAGNET_MEETING,\n { refetchQueries: MEETING_QUERIES, awaitRefetchQueries: true }\n );\n const [deleteEvent] = useMutation<{ deleteEvent: boolean }>(DELETE_CALENDAR_EVENT);\n\n const reset = () => {\n setTitle(defaultTitle);\n setKind(defaultKind);\n setCalendarId('');\n setStartLocal(defaultLocalStart());\n setDuration(30);\n setRecurrence('NONE');\n setAgenda('');\n setError(null);\n };\n\n const schedule = async () => {\n const problem = validateMeetingDraft({\n title,\n calendarId: effectiveCalendarId,\n startLocal,\n durationMinutes: duration,\n kind,\n milestoneId,\n });\n if (problem) {\n setError(problem);\n return;\n }\n const startIso = localInputValueToISO(startLocal);\n if (!startIso || !workspaceId) {\n setError('Pick a start time.');\n return;\n }\n setError(null);\n setSubmitting(true);\n try {\n const endIso = addMinutesIso(startIso, duration);\n const rrule = buildRRule(recurrence, startLocal);\n const meeting = await createAndLinkMeeting({\n createEvent,\n linkMeeting,\n deleteEvent,\n workspaceId,\n calendarId: effectiveCalendarId,\n title: title.trim(),\n startIso,\n endIso,\n kind,\n projectId,\n sprintId,\n milestoneId,\n agenda: agenda.trim(),\n rrule,\n timezone: viewerTimeZone(),\n });\n setOpen(false);\n reset();\n onScheduled?.(meeting);\n } catch (e) {\n setError(meetingScheduleErrorMessage(e));\n } finally {\n setSubmitting(false);\n }\n };\n\n const noCalendars = open && !calendarsLoading && calendars.length === 0;\n const contextLabel = milestoneId ? 'milestone' : sprintId ? 'sprint' : 'project';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {trigger ?? (\n <Button size=\"sm\" variant=\"outline\" data-testid=\"schedule-meeting-trigger\">\n <CalendarPlus className=\"mr-1 h-4 w-4\" />\n {triggerLabel}\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Schedule a meeting</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Creates a calendar event and links it to this {contextLabel}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n {noCalendars ? (\n <div className=\"space-y-2 text-sm\" data-testid=\"schedule-meeting-no-calendars\">\n <p className=\"text-muted-foreground\">\n You have no calendars yet. Create one in Calendar settings, then come back to schedule\n this meeting.\n </p>\n <a\n href=\"/calendar/settings\"\n className=\"text-primary hover:underline\"\n data-testid=\"schedule-meeting-calendar-settings-link\"\n >\n Open Calendar settings\n </a>\n </div>\n ) : (\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"schedule-meeting-title\" className=\"text-sm font-medium\">\n Title\n </label>\n <Input\n id=\"schedule-meeting-title\"\n className=\"mt-1\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n data-testid=\"schedule-meeting-title\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"schedule-meeting-kind\" className=\"text-sm font-medium\">\n Kind\n </label>\n <select\n id=\"schedule-meeting-kind\"\n className={fieldClass}\n value={kind}\n disabled={lockKind}\n onChange={(e) => setKind(e.target.value as MeetingKind)}\n data-testid=\"schedule-meeting-kind\"\n >\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=\"schedule-meeting-calendar\" className=\"text-sm font-medium\">\n Calendar\n </label>\n <select\n id=\"schedule-meeting-calendar\"\n className={fieldClass}\n value={effectiveCalendarId}\n disabled={calendarsLoading}\n onChange={(e) => setCalendarId(e.target.value)}\n data-testid=\"schedule-meeting-calendar\"\n >\n {calendarsLoading && <option value=\"\">Loading calendars…</option>}\n {calendars.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n {c.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"schedule-meeting-start\" className=\"text-sm font-medium\">\n Start\n </label>\n <Input\n id=\"schedule-meeting-start\"\n type=\"datetime-local\"\n className=\"mt-1\"\n value={startLocal}\n onChange={(e) => setStartLocal(e.target.value)}\n data-testid=\"schedule-meeting-start\"\n />\n </div>\n <div>\n <label htmlFor=\"schedule-meeting-duration\" className=\"text-sm font-medium\">\n Duration\n </label>\n <select\n id=\"schedule-meeting-duration\"\n className={fieldClass}\n value={duration}\n onChange={(e) => setDuration(Number(e.target.value))}\n data-testid=\"schedule-meeting-duration\"\n >\n {DURATION_OPTIONS_MINUTES.map((m) => (\n <option key={m} value={m}>\n {m < 60 ? `${m} minutes` : `${m / 60} hour${m === 60 ? '' : 's'}`}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"schedule-meeting-recurrence\" className=\"text-sm font-medium\">\n Repeats\n </label>\n <select\n id=\"schedule-meeting-recurrence\"\n className={fieldClass}\n value={recurrence}\n onChange={(e) => setRecurrence(e.target.value as RecurrenceOption)}\n data-testid=\"schedule-meeting-recurrence\"\n >\n {RECURRENCE_OPTIONS.map((r) => (\n <option key={r} value={r}>\n {RECURRENCE_LABELS[r]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"schedule-meeting-agenda\" className=\"text-sm font-medium\">\n Agenda (optional)\n </label>\n <Textarea\n id=\"schedule-meeting-agenda\"\n className=\"mt-1 min-h-[72px]\"\n value={agenda}\n onChange={(e) => setAgenda(e.target.value)}\n data-testid=\"schedule-meeting-agenda\"\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n )}\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n {!noCalendars && (\n <Button\n onClick={() => void schedule()}\n disabled={submitting}\n data-testid=\"schedule-meeting-submit\"\n >\n {submitting ? 'Scheduling…' : 'Schedule meeting'}\n </Button>\n )}\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unlinking a meeting removes PlanMagnet's own record of it (agenda/notes/action-item links) but — by
|
|
3
|
+
* default — leaves the calendar event itself alone, since the event may still matter to its attendees even
|
|
4
|
+
* once PlanMagnet stops tracking it. The opt-in checkbox deletes that event too, AS THE USER (same
|
|
5
|
+
* `deleteEvent` the create-link compensation path uses), so an "also delete" refusal is shown exactly like any
|
|
6
|
+
* other mutation refusal rather than silently leaving the event behind.
|
|
7
|
+
*/
|
|
8
|
+
export declare function UnlinkMeetingDialog({ meetingId, calendarEventId, meetingTitle, onUnlinked, }: {
|
|
9
|
+
meetingId: string;
|
|
10
|
+
calendarEventId: string;
|
|
11
|
+
meetingTitle: string;
|
|
12
|
+
onUnlinked: () => void;
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=UnlinkMeetingDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnlinkMeetingDialog.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/UnlinkMeetingDialog.tsx"],"names":[],"mappings":"AAwBA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,eAAe,EACf,YAAY,EACZ,UAAU,GACX,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,2CA2GA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mutationErrorMessage as e } from "../utils/graphqlErrors.js";
|
|
2
|
+
import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
|
|
3
|
+
import { ErrorLine as r } from "../controls/ConfirmButton.js";
|
|
4
|
+
import { DELETE_CALENDAR_EVENT as i, MEETING_QUERIES as a, UNLINK_PLANMAGNET_MEETING as o } from "./meetingGraphql.js";
|
|
5
|
+
import { useState as s } from "react";
|
|
6
|
+
import { Unlink as c } from "lucide-react";
|
|
7
|
+
import { useApolloClient as l, useMutation as u } from "@apollo/client/react";
|
|
8
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
|
+
import { Button as p, Checkbox as m, ResponsiveDialog as h, ResponsiveDialogContent as g, ResponsiveDialogDescription as _, ResponsiveDialogFooter as v, ResponsiveDialogHeader as y, ResponsiveDialogTitle as b, ResponsiveDialogTrigger as x } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { clearGraphqlFetchResponseCache as S } from "@burdenoff/fe-libs/shared/graphql";
|
|
11
|
+
//#region src/planmagnet/meetings/UnlinkMeetingDialog.tsx
|
|
12
|
+
function C({ meetingId: C, calendarEventId: w, meetingTitle: T, onUnlinked: E }) {
|
|
13
|
+
let [D, O] = s(!1), [k, A] = s(!1), [j, M] = s(null), N = l(), [P, F] = u(o, {
|
|
14
|
+
refetchQueries: a.filter((e) => e !== "GetPlanmagnetMeeting"),
|
|
15
|
+
context: t
|
|
16
|
+
}), [I, L] = u(i, { context: t }), R = F.loading || L.loading, z = async () => {
|
|
17
|
+
M(null);
|
|
18
|
+
try {
|
|
19
|
+
await n(P({ variables: { id: C } }));
|
|
20
|
+
} catch (t) {
|
|
21
|
+
M(e(t, "The meeting could not be unlinked."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (k) try {
|
|
25
|
+
await n(I({ variables: {
|
|
26
|
+
id: w,
|
|
27
|
+
scope: "ALL_EVENTS"
|
|
28
|
+
} }));
|
|
29
|
+
} catch (t) {
|
|
30
|
+
S(), N.refetchQueries({ include: ["GetPlanmagnetMeeting"] }), M("This meeting was unlinked, but the calendar event could not be deleted: " + e(t, "unknown reason"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
O(!1), E();
|
|
34
|
+
};
|
|
35
|
+
return /* @__PURE__ */ f(h, {
|
|
36
|
+
open: D,
|
|
37
|
+
onOpenChange: (e) => {
|
|
38
|
+
O(e), e || (A(!1), M(null));
|
|
39
|
+
},
|
|
40
|
+
children: [/* @__PURE__ */ d(x, {
|
|
41
|
+
asChild: !0,
|
|
42
|
+
children: /* @__PURE__ */ f(p, {
|
|
43
|
+
size: "sm",
|
|
44
|
+
variant: "outline",
|
|
45
|
+
"data-testid": "unlink-meeting-trigger",
|
|
46
|
+
children: [/* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }), "Unlink"]
|
|
47
|
+
})
|
|
48
|
+
}), /* @__PURE__ */ f(g, { children: [
|
|
49
|
+
/* @__PURE__ */ f(y, { children: [/* @__PURE__ */ f(b, { children: [
|
|
50
|
+
"Unlink \"",
|
|
51
|
+
T,
|
|
52
|
+
"\"?"
|
|
53
|
+
] }), /* @__PURE__ */ d(_, { children: "PlanMagnet stops tracking this meeting — its agenda, notes and action-item links are gone. The calendar event itself stays, unless you also delete it below." })] }),
|
|
54
|
+
/* @__PURE__ */ f("label", {
|
|
55
|
+
className: "flex cursor-pointer items-start gap-2 text-sm",
|
|
56
|
+
children: [/* @__PURE__ */ d(m, {
|
|
57
|
+
checked: k,
|
|
58
|
+
onCheckedChange: (e) => A(e === !0),
|
|
59
|
+
"data-testid": "unlink-meeting-also-delete-event"
|
|
60
|
+
}), /* @__PURE__ */ d("span", { children: "Also delete the calendar event (cannot be undone here)." })]
|
|
61
|
+
}),
|
|
62
|
+
j && /* @__PURE__ */ d(r, { message: j }),
|
|
63
|
+
/* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(p, {
|
|
64
|
+
variant: "outline",
|
|
65
|
+
onClick: () => O(!1),
|
|
66
|
+
disabled: R,
|
|
67
|
+
children: "Keep it"
|
|
68
|
+
}), /* @__PURE__ */ d(p, {
|
|
69
|
+
onClick: () => void z(),
|
|
70
|
+
disabled: R,
|
|
71
|
+
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
72
|
+
"data-testid": "unlink-meeting-confirm",
|
|
73
|
+
children: R ? "Unlinking…" : "Unlink meeting"
|
|
74
|
+
})] })
|
|
75
|
+
] })]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { C as UnlinkMeetingDialog };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=UnlinkMeetingDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnlinkMeetingDialog.js","names":[],"sources":["../../../src/planmagnet/meetings/UnlinkMeetingDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useApolloClient, useMutation } from '@apollo/client/react';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n Button,\n Checkbox,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { Unlink } from 'lucide-react';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport {\n UNLINK_PLANMAGNET_MEETING,\n DELETE_CALENDAR_EVENT,\n MEETING_QUERIES,\n} from './meetingGraphql';\n\n/**\n * Unlinking a meeting removes PlanMagnet's own record of it (agenda/notes/action-item links) but — by\n * default — leaves the calendar event itself alone, since the event may still matter to its attendees even\n * once PlanMagnet stops tracking it. The opt-in checkbox deletes that event too, AS THE USER (same\n * `deleteEvent` the create-link compensation path uses), so an \"also delete\" refusal is shown exactly like any\n * other mutation refusal rather than silently leaving the event behind.\n */\nexport function UnlinkMeetingDialog({\n meetingId,\n calendarEventId,\n meetingTitle,\n onUnlinked,\n}: {\n meetingId: string;\n calendarEventId: string;\n meetingTitle: string;\n onUnlinked: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const [alsoDeleteEvent, setAlsoDeleteEvent] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const client = useApolloClient();\n // Excludes 'GetPlanmagnetMeeting' — this PAGE's own query, for the meeting we are about to leave. Refetching\n // it would re-run against a now-unlinked meeting (see `LEAVES_PAGE_CONTEXT`'s doc comment); the list page\n // this navigates to reads 'ListPlanmagnetMeetings' fresh on its own mount regardless. That reasoning only\n // holds on the SUCCESS path, though — when the opt-in `deleteEvent` compensation below fails, we deliberately\n // stay on this page, and `GetPlanmagnetMeeting` is force-refetched there instead (see that catch block).\n const [unlink, unlinkState] = useMutation(UNLINK_PLANMAGNET_MEETING, {\n refetchQueries: MEETING_QUERIES.filter((q) => q !== 'GetPlanmagnetMeeting'),\n context: LEAVES_PAGE_CONTEXT,\n });\n // Only ever called right after a successful unlink, on the way OUT of this page (see `confirm` below) — same\n // `LEAVES_PAGE_CONTEXT` reasoning as `unlink` above applies here too.\n const [deleteEvent, deleteState] = useMutation(DELETE_CALENDAR_EVENT, {\n context: LEAVES_PAGE_CONTEXT,\n });\n const busy = unlinkState.loading || deleteState.loading;\n\n const confirm = async () => {\n setError(null);\n try {\n await throwOnError(unlink({ variables: { id: meetingId } }));\n } catch (e) {\n setError(mutationErrorMessage(e, 'The meeting could not be unlinked.'));\n return;\n }\n if (alsoDeleteEvent) {\n try {\n await throwOnError(\n deleteEvent({ variables: { id: calendarEventId, scope: 'ALL_EVENTS' } })\n );\n } catch (e) {\n // The unlink already succeeded — say so, rather than leaving a \"nothing happened\" impression when\n // PlanMagnet in fact no longer tracks this meeting; only the calendar event itself stayed behind.\n // Force this page's own `GetPlanmagnetMeeting` to refetch now (it was deliberately excluded from\n // `unlink`'s refetchQueries above, for the success path that navigates away before a refetch would\n // matter — this path doesn't navigate away, so the page must not keep showing the fully-populated,\n // now-stale meeting). `planmagnetMeeting(id)` is nullable and resolves to null for an unlinked\n // meeting, so this refetch falls through to the page's existing \"Meeting not found\" state. The cache\n // drop must come first: fe-libs' 15 s fetch-layer response cache would otherwise replay the\n // pre-unlink read if the refetch lands inside that window (same reasoning as `useLiveRefresh.ts`).\n clearGraphqlFetchResponseCache();\n void client.refetchQueries({ include: ['GetPlanmagnetMeeting'] });\n setError(\n `This meeting was unlinked, but the calendar event could not be deleted: ` +\n mutationErrorMessage(e, 'unknown reason')\n );\n return;\n }\n }\n setOpen(false);\n onUnlinked();\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) {\n setAlsoDeleteEvent(false);\n setError(null);\n }\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"unlink-meeting-trigger\">\n <Unlink className=\"mr-1 h-4 w-4\" />\n Unlink\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Unlink "{meetingTitle}"?</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n PlanMagnet stops tracking this meeting — its agenda, notes and action-item links are\n gone. The calendar event itself stays, unless you also delete it below.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <Checkbox\n checked={alsoDeleteEvent}\n onCheckedChange={(checked) => setAlsoDeleteEvent(checked === true)}\n data-testid=\"unlink-meeting-also-delete-event\"\n />\n <span>Also delete the calendar event (cannot be undone here).</span>\n </label>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)} disabled={busy}>\n Keep it\n </Button>\n <Button\n onClick={() => void confirm()}\n disabled={busy}\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n data-testid=\"unlink-meeting-confirm\"\n >\n {busy ? 'Unlinking…' : 'Unlink meeting'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;AA+BA,SAAgB,EAAoB,EAClC,cACA,oBACA,iBACA,iBAMC;CACD,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAS,GAAiB,EAM1B,CAAC,GAAQ,KAAe,EAAY,GAA2B;EACnE,gBAAgB,EAAgB,QAAQ,MAAM,MAAM,uBAAuB;EAC3E,SAAS;EACV,CAAC,EAGI,CAAC,GAAa,KAAe,EAAY,GAAuB,EACpE,SAAS,GACV,CAAC,EACI,IAAO,EAAY,WAAW,EAAY,SAE1C,IAAU,YAAY;AAC1B,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC;WACrD,GAAG;AACV,KAAS,EAAqB,GAAG,qCAAqC,CAAC;AACvE;;AAEF,MAAI,EACF,KAAI;AACF,SAAM,EACJ,EAAY,EAAE,WAAW;IAAE,IAAI;IAAiB,OAAO;IAAc,EAAE,CAAC,CACzE;WACM,GAAG;AAYV,GAFA,GAAgC,EAC3B,EAAO,eAAe,EAAE,SAAS,CAAC,uBAAuB,EAAE,CAAC,EACjE,EACE,6EACE,EAAqB,GAAG,iBAAiB,CAC5C;AACD;;AAIJ,EADA,EAAQ,GAAM,EACd,GAAY;;AAGd,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACR,MACH,EAAmB,GAAM,EACzB,EAAS,KAAK;;YANpB,CAUE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,SAE5B;;GACe,CAAA,EAC1B,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA;IAAuB;IAAc;IAAa;IAA+B,EAAA,CAAA,EACjF,kBAAC,GAAD,EAAA,UAA6B,gKAGC,CAAA,CACP,EAAA,CAAA;GACzB,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,GAAD;KACE,SAAS;KACT,kBAAkB,MAAY,EAAmB,MAAY,GAAK;KAClE,eAAY;KACZ,CAAA,EACF,kBAAC,QAAD,EAAA,UAAM,2DAA8D,CAAA,CAC9D;;GACP,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;GACvC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAQ,GAAM;IAAE,UAAU;cAAM;IAEhE,CAAA,EACT,kBAAC,GAAD;IACE,eAAe,KAAK,GAAS;IAC7B,UAAU;IACV,WAAU;IACV,eAAY;cAEX,IAAO,eAAe;IAChB,CAAA,CACc,EAAA,CAAA;GACD,EAAA,CAAA,CACT"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { MeetingKind } from './meetingModel';
|
|
2
|
+
|
|
3
|
+
/** The slice of an Apollo `useMutation` tuple's first element this helper needs — typed narrowly so a caller can
|
|
4
|
+
* pass its real mutate function straight through without an adapter. */
|
|
5
|
+
type MutateFn<TData> = (options: {
|
|
6
|
+
variables: Record<string, unknown>;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
data?: TData;
|
|
9
|
+
error?: unknown;
|
|
10
|
+
}>;
|
|
11
|
+
export interface CreateAndLinkMeetingParams {
|
|
12
|
+
createEvent: MutateFn<{
|
|
13
|
+
createEvent: {
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
linkMeeting: MutateFn<{
|
|
18
|
+
planmagnetLinkMeeting: {
|
|
19
|
+
id: string;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
22
|
+
deleteEvent: MutateFn<{
|
|
23
|
+
deleteEvent: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
workspaceId: string;
|
|
26
|
+
calendarId: string;
|
|
27
|
+
title: string;
|
|
28
|
+
startIso: string;
|
|
29
|
+
endIso: string;
|
|
30
|
+
kind: MeetingKind;
|
|
31
|
+
projectId: string;
|
|
32
|
+
sprintId?: string | null;
|
|
33
|
+
milestoneId?: string | null;
|
|
34
|
+
agenda?: string;
|
|
35
|
+
rrule?: string;
|
|
36
|
+
timezone: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates the calendar event AS THE USER, then records the PlanMagnet link — the architecture every meeting
|
|
40
|
+
* feature in this MFE is built on (`PlanmagnetMeeting`'s doc comment, `graphql/wspace.graphql`:
|
|
41
|
+
* `planmagnetLinkMeeting` "never calls the Calendar API itself; it only validates and stores the link").
|
|
42
|
+
*
|
|
43
|
+
* If the link is refused, the event just created is deleted (best-effort — `ALL_EVENTS` scope, since an
|
|
44
|
+
* unlinked event was never a committed series) so a failed "schedule" never leaves an orphaned calendar event
|
|
45
|
+
* behind. A compensation failure is swallowed deliberately: the caller must see the LINK refusal (the real
|
|
46
|
+
* reason scheduling failed), never a secondary "cleanup also failed" message in its place.
|
|
47
|
+
*
|
|
48
|
+
* Both call sites (`ScheduleMeetingDialog`, `ScheduleCeremoniesDialog`) await this already wrapped in
|
|
49
|
+
* `throwOnError` semantics — every mutation inside is itself awaited through `throwOnError`, so nothing here
|
|
50
|
+
* can resolve silently on a refusal.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createAndLinkMeeting(p: CreateAndLinkMeetingParams): Promise<{
|
|
53
|
+
id: string;
|
|
54
|
+
}>;
|
|
55
|
+
export declare function meetingScheduleErrorMessage(e: unknown): string;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=meetingActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meetingActions.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/meetingActions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;yEACyE;AACzE,KAAK,QAAQ,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE;IAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,KAAK,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,QAAQ,CAAC;QAAE,WAAW,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IACvD,WAAW,EAAE,QAAQ,CAAC;QAAE,qBAAqB,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IACjE,WAAW,EAAE,QAAQ,CAAC;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,0BAA0B,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAsDjG;AAED,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mutationErrorMessage as e } from "../utils/graphqlErrors.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
//#region src/planmagnet/meetings/meetingActions.ts
|
|
4
|
+
async function n(e) {
|
|
5
|
+
let n = (await t(e.createEvent({ variables: { input: {
|
|
6
|
+
calendarId: e.calendarId,
|
|
7
|
+
title: e.title,
|
|
8
|
+
startTime: e.startIso,
|
|
9
|
+
endTime: e.endIso,
|
|
10
|
+
eventType: "MEETING",
|
|
11
|
+
rrule: e.rrule,
|
|
12
|
+
isRecurring: !!e.rrule,
|
|
13
|
+
timezone: e.timezone,
|
|
14
|
+
description: e.agenda || void 0
|
|
15
|
+
} } }))).data?.createEvent.id;
|
|
16
|
+
if (!n) throw Error("The calendar event was not created.");
|
|
17
|
+
try {
|
|
18
|
+
let r = (await t(e.linkMeeting({ variables: {
|
|
19
|
+
workspaceId: e.workspaceId,
|
|
20
|
+
input: {
|
|
21
|
+
calendarEventId: n,
|
|
22
|
+
title: e.title,
|
|
23
|
+
kind: e.kind,
|
|
24
|
+
projectId: e.projectId,
|
|
25
|
+
sprintId: e.sprintId || void 0,
|
|
26
|
+
milestoneId: e.milestoneId || void 0,
|
|
27
|
+
startsAt: e.startIso,
|
|
28
|
+
endsAt: e.endIso,
|
|
29
|
+
agenda: e.agenda || void 0,
|
|
30
|
+
recurrenceRule: e.rrule
|
|
31
|
+
}
|
|
32
|
+
} }))).data?.planmagnetLinkMeeting;
|
|
33
|
+
if (!r) throw Error("The meeting could not be linked.");
|
|
34
|
+
return r;
|
|
35
|
+
} catch (r) {
|
|
36
|
+
try {
|
|
37
|
+
await t(e.deleteEvent({ variables: {
|
|
38
|
+
id: n,
|
|
39
|
+
scope: "ALL_EVENTS"
|
|
40
|
+
} }));
|
|
41
|
+
} catch {}
|
|
42
|
+
throw r;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function r(t) {
|
|
46
|
+
return e(t, "The meeting could not be scheduled.");
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { n as createAndLinkMeeting, r as meetingScheduleErrorMessage };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=meetingActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meetingActions.js","names":[],"sources":["../../../src/planmagnet/meetings/meetingActions.ts"],"sourcesContent":["import { throwOnError } from '../utils/mutationResult';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport type { MeetingKind } from './meetingModel';\n\n/** The slice of an Apollo `useMutation` tuple's first element this helper needs — typed narrowly so a caller can\n * pass its real mutate function straight through without an adapter. */\ntype MutateFn<TData> = (options: {\n variables: Record<string, unknown>;\n}) => Promise<{ data?: TData; error?: unknown }>;\n\nexport interface CreateAndLinkMeetingParams {\n createEvent: MutateFn<{ createEvent: { id: string } }>;\n linkMeeting: MutateFn<{ planmagnetLinkMeeting: { id: string } }>;\n deleteEvent: MutateFn<{ deleteEvent: boolean }>;\n workspaceId: string;\n calendarId: string;\n title: string;\n startIso: string;\n endIso: string;\n kind: MeetingKind;\n projectId: string;\n sprintId?: string | null;\n milestoneId?: string | null;\n agenda?: string;\n rrule?: string;\n timezone: string;\n}\n\n/**\n * Creates the calendar event AS THE USER, then records the PlanMagnet link — the architecture every meeting\n * feature in this MFE is built on (`PlanmagnetMeeting`'s doc comment, `graphql/wspace.graphql`:\n * `planmagnetLinkMeeting` \"never calls the Calendar API itself; it only validates and stores the link\").\n *\n * If the link is refused, the event just created is deleted (best-effort — `ALL_EVENTS` scope, since an\n * unlinked event was never a committed series) so a failed \"schedule\" never leaves an orphaned calendar event\n * behind. A compensation failure is swallowed deliberately: the caller must see the LINK refusal (the real\n * reason scheduling failed), never a secondary \"cleanup also failed\" message in its place.\n *\n * Both call sites (`ScheduleMeetingDialog`, `ScheduleCeremoniesDialog`) await this already wrapped in\n * `throwOnError` semantics — every mutation inside is itself awaited through `throwOnError`, so nothing here\n * can resolve silently on a refusal.\n */\nexport async function createAndLinkMeeting(p: CreateAndLinkMeetingParams): Promise<{ id: string }> {\n const eventResult = await throwOnError(\n p.createEvent({\n variables: {\n input: {\n calendarId: p.calendarId,\n title: p.title,\n startTime: p.startIso,\n endTime: p.endIso,\n eventType: 'MEETING',\n rrule: p.rrule,\n isRecurring: Boolean(p.rrule),\n timezone: p.timezone,\n description: p.agenda || undefined,\n },\n },\n })\n );\n const calendarEventId = eventResult.data?.createEvent.id;\n if (!calendarEventId) throw new Error('The calendar event was not created.');\n\n try {\n const linkResult = await throwOnError(\n p.linkMeeting({\n variables: {\n workspaceId: p.workspaceId,\n input: {\n calendarEventId,\n title: p.title,\n kind: p.kind,\n projectId: p.projectId,\n sprintId: p.sprintId || undefined,\n milestoneId: p.milestoneId || undefined,\n startsAt: p.startIso,\n endsAt: p.endIso,\n agenda: p.agenda || undefined,\n recurrenceRule: p.rrule,\n },\n },\n })\n );\n const meeting = linkResult.data?.planmagnetLinkMeeting;\n if (!meeting) throw new Error('The meeting could not be linked.');\n return meeting;\n } catch (linkError) {\n try {\n await throwOnError(\n p.deleteEvent({ variables: { id: calendarEventId, scope: 'ALL_EVENTS' } })\n );\n } catch {\n // Best-effort compensation only — see doc comment above.\n }\n throw linkError;\n }\n}\n\nexport function meetingScheduleErrorMessage(e: unknown): string {\n return mutationErrorMessage(e, 'The meeting could not be scheduled.');\n}\n"],"mappings":";;;AA0CA,eAAsB,EAAqB,GAAwD;CAkBjG,IAAM,KAjBc,MAAM,EACxB,EAAE,YAAY,EACZ,WAAW,EACT,OAAO;EACL,YAAY,EAAE;EACd,OAAO,EAAE;EACT,WAAW,EAAE;EACb,SAAS,EAAE;EACX,WAAW;EACX,OAAO,EAAE;EACT,aAAa,EAAQ,EAAE;EACvB,UAAU,EAAE;EACZ,aAAa,EAAE,UAAU,KAAA;EAC1B,EACF,EACF,CAAC,CACH,EACmC,MAAM,YAAY;AACtD,KAAI,CAAC,EAAiB,OAAU,MAAM,sCAAsC;AAE5E,KAAI;EAoBF,IAAM,KAnBa,MAAM,EACvB,EAAE,YAAY,EACZ,WAAW;GACT,aAAa,EAAE;GACf,OAAO;IACL;IACA,OAAO,EAAE;IACT,MAAM,EAAE;IACR,WAAW,EAAE;IACb,UAAU,EAAE,YAAY,KAAA;IACxB,aAAa,EAAE,eAAe,KAAA;IAC9B,UAAU,EAAE;IACZ,QAAQ,EAAE;IACV,QAAQ,EAAE,UAAU,KAAA;IACpB,gBAAgB,EAAE;IACnB;GACF,EACF,CAAC,CACH,EAC0B,MAAM;AACjC,MAAI,CAAC,EAAS,OAAU,MAAM,mCAAmC;AACjE,SAAO;UACA,GAAW;AAClB,MAAI;AACF,SAAM,EACJ,EAAE,YAAY,EAAE,WAAW;IAAE,IAAI;IAAiB,OAAO;IAAc,EAAE,CAAC,CAC3E;UACK;AAGR,QAAM;;;AAIV,SAAgB,EAA4B,GAAoB;AAC9D,QAAO,EAAqB,GAAG,sCAAsC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL documents for PlanMagnet meetings (GAP-06, CWF-22/23), from two different subgraphs:
|
|
3
|
+
*
|
|
4
|
+
* - The platform **Calendar** module (`calendars`, `createEvent`, `deleteEvent`) — a meeting's real schedule
|
|
5
|
+
* lives here. The UI creates the calendar event AS THE USER, then links it below.
|
|
6
|
+
* - **PlanMagnet** itself (`planmagnetMeeting(s)`, `planmagnetLinkMeeting`, `planmagnetUpdateMeeting`,
|
|
7
|
+
* `planmagnetUnlinkMeeting`, the action-item mutations) — PlanMagnet-owned content (agenda/notes/kind) plus a
|
|
8
|
+
* read-only snapshot of the schedule for listing/sorting. See `PlanmagnetMeeting`'s doc comment in
|
|
9
|
+
* `graphql/wspace.graphql` for the full split, and `meetingActions.ts` for how the two are composed.
|
|
10
|
+
*
|
|
11
|
+
* Selection sets are written out in full rather than shared via fragment interpolation, same convention as
|
|
12
|
+
* `reports/reportGraphql.ts` (one literal per `gql` call; validated by `bun run validate:schema` against
|
|
13
|
+
* `graphql/wspace.graphql`).
|
|
14
|
+
*/
|
|
15
|
+
export declare const SCHEDULE_MEETING_CALENDARS: import('graphql').DocumentNode;
|
|
16
|
+
export declare const CREATE_CALENDAR_EVENT: import('graphql').DocumentNode;
|
|
17
|
+
export declare const DELETE_CALENDAR_EVENT: import('graphql').DocumentNode;
|
|
18
|
+
/** Every query a meeting write should refresh. */
|
|
19
|
+
export declare const MEETING_QUERIES: string[];
|
|
20
|
+
export declare const LIST_PLANMAGNET_MEETINGS: import('graphql').DocumentNode;
|
|
21
|
+
export declare const GET_PLANMAGNET_MEETING: import('graphql').DocumentNode;
|
|
22
|
+
export declare const LINK_PLANMAGNET_MEETING: import('graphql').DocumentNode;
|
|
23
|
+
export declare const UPDATE_PLANMAGNET_MEETING: import('graphql').DocumentNode;
|
|
24
|
+
export declare const UNLINK_PLANMAGNET_MEETING: import('graphql').DocumentNode;
|
|
25
|
+
export declare const ADD_MEETING_ACTION_ITEM: import('graphql').DocumentNode;
|
|
26
|
+
export declare const REMOVE_MEETING_ACTION_ITEM: import('graphql').DocumentNode;
|
|
27
|
+
/** Minimal create, for "Add action item" (a brand-new work item attached to the meeting it was raised in). */
|
|
28
|
+
export declare const CREATE_MEETING_ACTION_WORK_ITEM: import('graphql').DocumentNode;
|
|
29
|
+
//# sourceMappingURL=meetingGraphql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meetingGraphql.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/meetingGraphql.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAIH,eAAO,MAAM,0BAA0B,gCAatC,CAAC;AAEF,eAAO,MAAM,qBAAqB,gCAQjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,gCAIjC,CAAC;AAIF,kDAAkD;AAClD,eAAO,MAAM,eAAe,UAAqD,CAAC;AAElF,eAAO,MAAM,wBAAwB,gCAiCpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCA2ClC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAenC,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAarC,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAIrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAanC,CAAC;AAEF,eAAO,MAAM,0BAA0B,gCAItC,CAAC;AAEF,8GAA8G;AAC9G,eAAO,MAAM,+BAA+B,gCAS3C,CAAC"}
|