@burdenoff/microfe-planmagnet 2026.1001.4 → 2026.1001.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"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"}
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,2CAqRA"}
@@ -212,7 +212,7 @@ function b({ projectId: b, sprintId: x = null, milestoneId: S = null, defaultKin
212
212
  children: "Cancel"
213
213
  }), !$ && /* @__PURE__ */ h(_, {
214
214
  onClick: () => void Ce(),
215
- disabled: K,
215
+ disabled: K || J || !Z,
216
216
  "data-testid": "schedule-meeting-submit",
217
217
  children: K ? "Scheduling…" : "Schedule meeting"
218
218
  })] })
@@ -1 +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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"}
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 // Not before the calendars have loaded: until then the default calendar is unknown, and a click\n // would be refused with \"Pick a calendar.\" just before the default appears in the picker.\n disabled={submitting || calendarsLoading || !effectiveCalendarId}\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.1001.4",
3
+ "version": "2026.1001.5",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [