@open-mercato/core 0.6.8-develop.7063.1.664341cf65 → 0.6.8-develop.7065.1.b7b351a126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/interactions/route.js +3 -1
  3. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  4. package/dist/modules/customers/commands/interactions.js.map +1 -1
  5. package/dist/modules/customers/components/calendar/AgendaList.js +2 -2
  6. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  7. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
  8. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  9. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
  10. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  11. package/dist/modules/customers/components/calendar/EventBlock.js +1 -1
  12. package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
  13. package/dist/modules/customers/components/calendar/editor/PeopleField.js +40 -5
  14. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  15. package/dist/modules/customers/components/calendar/editor/hooks.js +2 -1
  16. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/types.js +1 -1
  18. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  19. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -4
  20. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  21. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +3 -3
  22. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  23. package/dist/modules/customers/data/entities.js.map +1 -1
  24. package/dist/modules/customers/data/validators.js +22 -1
  25. package/dist/modules/customers/data/validators.js.map +2 -2
  26. package/dist/modules/customers/lib/calendar/conflicts.js +8 -2
  27. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  28. package/dist/modules/customers/lib/calendar/editorPayload.js +3 -2
  29. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  30. package/dist/modules/customers/lib/calendar/mapItem.js +8 -3
  31. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  32. package/dist/modules/customers/lib/calendar/participantIdentity.js +9 -0
  33. package/dist/modules/customers/lib/calendar/participantIdentity.js.map +7 -0
  34. package/package.json +7 -7
  35. package/src/modules/customers/api/interactions/route.ts +8 -2
  36. package/src/modules/customers/commands/interactions.ts +1 -1
  37. package/src/modules/customers/components/calendar/AgendaList.tsx +3 -3
  38. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
  39. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
  40. package/src/modules/customers/components/calendar/EventBlock.tsx +2 -2
  41. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +61 -5
  42. package/src/modules/customers/components/calendar/editor/hooks.ts +7 -1
  43. package/src/modules/customers/components/calendar/types.ts +2 -2
  44. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +5 -5
  45. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +5 -5
  46. package/src/modules/customers/components/detail/types.ts +1 -1
  47. package/src/modules/customers/data/entities.ts +1 -1
  48. package/src/modules/customers/data/validators.ts +32 -6
  49. package/src/modules/customers/i18n/de.json +4 -0
  50. package/src/modules/customers/i18n/en.json +4 -0
  51. package/src/modules/customers/i18n/es.json +4 -0
  52. package/src/modules/customers/i18n/ko.json +4 -0
  53. package/src/modules/customers/i18n/pl.json +4 -0
  54. package/src/modules/customers/lib/calendar/conflicts.ts +10 -2
  55. package/src/modules/customers/lib/calendar/editorPayload.ts +4 -3
  56. package/src/modules/customers/lib/calendar/mapItem.ts +8 -3
  57. package/src/modules/customers/lib/calendar/participantIdentity.ts +17 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/CalendarEventEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { CrudForm, type CrudFormGroup, type CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n buildEditorTypeOptions,\n buildInteractionPayload,\n computeDurationMinutes,\n createDefaultFormState,\n defaultRepeatDaysForDateInput,\n editorKindOfInteractionType,\n KIND_CONFIG,\n parseItemToFormState,\n resolveSavedOwnerUserId,\n type EditorFormState,\n type EditorKind,\n type EditorPriority,\n} from '../../lib/calendar/editorPayload'\nimport type { ConflictScope } from '../../lib/calendar/preferences'\nimport { renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { normalizeCustomFieldSubmitValue } from '../detail/customFieldUtils'\nimport type { CalendarItem } from './types'\nimport { EDITOR_SCROLL_EVENT, Field } from './editor/inputs'\nimport { SegmentGroup } from './editor/SegmentGroup'\nimport { PriorityField } from './editor/PriorityField'\nimport { RelatedToField } from './editor/RelatedToField'\nimport { RepeatField } from './editor/RepeatField'\nimport { PeopleField } from './editor/PeopleField'\nimport { ResourcesField } from './editor/ResourcesField'\nimport { ScheduleSection } from './editor/ScheduleSection'\nimport { LocationField } from './editor/LocationField'\nimport { useConflictProbe, useEditorLabelResolution } from './editor/hooks'\n\nexport interface CalendarEventEditorProps {\n open: boolean\n mode: 'create' | 'edit'\n item?: CalendarItem | null\n defaultDate?: Date\n defaultRange?: { start: Date; end: Date } | null\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n conflictScope?: ConflictScope\n currentUserId?: string | null\n resourcesEnabled?: boolean\n staffEnabled?: boolean\n onOpenChange(open: boolean): void\n onSaved(): void\n}\n\nconst FORM_ID = 'customers-calendar-event-editor'\nconst INTERACTION_ENTITY_IDS = [E.customers.customer_interaction]\n\nconst PEOPLE_FIELD_TEXT = {\n attendees: { labelKey: 'customers.calendar.editor.attendees', label: 'Attendees', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer\u2026' },\n participants: { labelKey: 'customers.calendar.editor.participants', label: 'Participants', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer\u2026' },\n to: { labelKey: 'customers.calendar.editor.to', label: 'To', placeholderKey: 'customers.calendar.editor.addRecipientPlaceholder', placeholder: 'Add recipient\u2026' },\n} as const\n\n// CrudForm values carry the flattened EditorFormState keys (seeded via\n// initialValues) plus cf_* custom-field keys managed by CrudForm itself.\nfunction formStateOfValues(values: Record<string, unknown>): EditorFormState {\n return values as unknown as EditorFormState\n}\n\nfunction customFieldInitialValues(item: CalendarItem): Record<string, unknown> {\n const customValues = (item.raw as Record<string, unknown>).customValues\n if (!customValues || typeof customValues !== 'object' || Array.isArray(customValues)) return {}\n return Object.fromEntries(\n Object.entries(customValues as Record<string, unknown>).map(([key, value]) => [`cf_${key}`, value]),\n )\n}\n\ntype EditorBodyProps = {\n ctx: CrudFormGroupComponentProps\n open: boolean\n isEdit: boolean\n item?: CalendarItem | null\n typeLabels: Record<string, string>\n typeIcons: Record<string, string | null>\n conflictScope: ConflictScope\n currentUserId: string | null\n resourcesEnabled: boolean\n staffEnabled: boolean\n}\n\nfunction EditorBody({\n ctx,\n open,\n isEdit,\n item,\n typeLabels,\n typeIcons,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n}: EditorBodyProps) {\n const t = useT()\n const locale = useLocale()\n const { setValue, errors } = ctx\n const form = formStateOfValues(ctx.values)\n const config = KIND_CONFIG[form.kind]\n\n const update = React.useCallback(\n (patch: Partial<EditorFormState>) => {\n for (const [key, value] of Object.entries(patch)) setValue(key, value)\n },\n [setValue],\n )\n\n useEditorLabelResolution(open, form, update)\n // Probe against the owner the interaction will actually be SAVED with so the\n // editor warning matches the grid's post-save conflict rings: tasks own via\n // their assignee, other kinds stay ownerless on create / keep the existing\n // owner on edit (conflicts then come from shared participants).\n const draftOwnerUserId = resolveSavedOwnerUserId(config, form, isEdit, item?.ownerUserId ?? null)\n // Self-exclude by the underlying interaction id (raw.id): findEditorConflictItems\n // drops candidates by raw.id, and every expanded occurrence of a recurring series\n // shares it \u2014 so the edited record never conflicts with itself.\n const conflict = useConflictProbe(open, form, config, isEdit && item ? item.raw.id : null, draftOwnerUserId, conflictScope, currentUserId)\n\n const kindLabels = React.useMemo(\n () => ({\n meeting: t('customers.calendar.editor.types.meeting', 'meeting'),\n call: t('customers.calendar.editor.types.call', 'call'),\n email: t('customers.calendar.editor.types.email', 'email'),\n note: t('customers.calendar.editor.types.note', 'note'),\n event: t('customers.calendar.editor.types.event', 'event'),\n task: t('customers.calendar.editor.types.task', 'task'),\n }),\n [t],\n ) satisfies Record<EditorKind, string>\n\n const selectedType = form.category ?? form.kind\n const typeOptions = React.useMemo(\n () => buildEditorTypeOptions({ typeLabels, typeIcons, selectedValue: selectedType, kindLabels }),\n [typeLabels, typeIcons, selectedType, kindLabels],\n )\n const typeSwitcherOptions = React.useMemo(\n () =>\n typeOptions.map((option) => ({\n value: option.value,\n label: option.label,\n icon: renderDictionaryIcon(option.icon, 'h-4 w-4'),\n })),\n [typeOptions],\n )\n\n // Candidates for the Call \"insert phone from contact\" button: the linked\n // person (companies have no phone) + customer attendees. Their userId is the\n // person id, so the picker can resolve primary_phone from the people API.\n const phoneContactIds = React.useMemo(() => {\n const ids: string[] = []\n if (form.relatedTo && form.relatedTo.kind !== 'company') ids.push(form.relatedTo.id)\n for (const participant of form.participants) {\n if (participant.isCustomer) ids.push(participant.userId)\n }\n return Array.from(new Set(ids))\n }, [form.relatedTo, form.participants])\n\n const titleLabel = form.kind === 'email'\n ? t('customers.calendar.editor.titleLabel.email', 'Subject')\n : form.kind === 'note'\n ? t('customers.calendar.editor.titleLabel.note', 'Note')\n : t('customers.calendar.editor.titleLabel.generic', 'Title')\n\n return (\n // Single column on phones (full-screen sheet). On lg+ the dialog widens and\n // the fields group into two thematic columns: WHEN (schedule + repeat) on\n // the left, CONTEXT (related record, category, location) on the right;\n // people/resources and the task fields pair up below; title, description\n // and the type switcher span both columns.\n <div className=\"grid w-full grid-cols-1 items-start gap-4 lg:grid-cols-2 lg:gap-x-6\">\n {conflict ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertTitle>{t('customers.calendar.editor.conflictTitle', 'Calendar conflict')}</AlertTitle>\n <AlertDescription>{conflict}</AlertDescription>\n </Alert>\n ) : null}\n <div className=\"w-full lg:col-span-2\">\n <SegmentGroup<string>\n ariaLabel={t('customers.calendar.editor.typeSwitcher', 'Event type')}\n value={selectedType}\n onChange={(type) => update({ kind: editorKindOfInteractionType(type), category: type })}\n options={typeSwitcherOptions}\n />\n </div>\n <Field label={titleLabel} error={errors.title} className=\"lg:col-span-2\">\n <Input\n type=\"text\"\n value={form.title}\n onChange={(event) => update({ title: event.target.value })}\n placeholder={t('customers.calendar.editor.titlePlaceholder', 'Add a title\u2026')}\n aria-label={titleLabel}\n autoFocus\n size=\"lg\"\n />\n </Field>\n <div className=\"flex w-full flex-col gap-4\">\n <ScheduleSection\n dateLabel={config.dateLabel}\n hasAllDay={config.hasAllDay}\n hasEnd={config.hasEnd}\n allDay={form.allDay}\n date={form.date}\n startTime={form.startTime}\n endDate={form.endDate}\n endTime={form.endTime}\n locale={locale}\n endsError={errors.ends}\n onAllDayChange={(allDay) => update({ allDay })}\n onDateChange={(date) => {\n const untouchedDefault =\n JSON.stringify(form.repeatDays) === JSON.stringify(defaultRepeatDaysForDateInput(form.date))\n update(\n untouchedDefault\n ? { date, repeatDays: defaultRepeatDaysForDateInput(date) }\n : { date },\n )\n }}\n onStartTimeChange={(startTime) => update({ startTime })}\n onEndDateChange={(endDate) => update({ endDate })}\n onEndTimeChange={(endTime) => update({ endTime })}\n />\n {config.hasRepeat ? (\n <RepeatField\n freq={form.repeatFreq}\n days={form.repeatDays}\n endType={form.repeatEndType}\n count={form.repeatCount}\n untilDate={form.repeatUntilDate}\n locale={locale}\n onFreqChange={(repeatFreq) =>\n update(\n repeatFreq === 'weekly'\n ? { repeatFreq, repeatDays: defaultRepeatDaysForDateInput(form.date) }\n : { repeatFreq },\n )}\n onToggleDay={(index) =>\n update({ repeatDays: form.repeatDays.map((active, dayIndex) => (dayIndex === index ? !active : active)) })}\n onEndTypeChange={(repeatEndType) => update({ repeatEndType })}\n onCountChange={(repeatCount) => update({ repeatCount })}\n onUntilDateChange={(repeatUntilDate) => update({ repeatUntilDate })}\n />\n ) : null}\n {config.people && config.people !== 'assignee' ? (\n <Field label={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}>\n <PeopleField\n mode=\"multi\"\n includeCustomers\n includeStaff={staffEnabled}\n placeholder={t(PEOPLE_FIELD_TEXT[config.people].placeholderKey, PEOPLE_FIELD_TEXT[config.people].placeholder)}\n ariaLabel={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}\n value={form.participants}\n onChange={(participants) => update({ participants })}\n />\n </Field>\n ) : null}\n {config.people === 'assignee' && staffEnabled ? (\n <Field label={t('customers.calendar.editor.assignee', 'Assignee')} error={errors.assignee}>\n <PeopleField\n mode=\"single\"\n includeCustomers={false}\n includeStaff\n placeholder={t('customers.calendar.editor.assigneePlaceholder', 'Assign to a team member\u2026')}\n ariaLabel={t('customers.calendar.editor.assignee', 'Assignee')}\n value={form.assigneeUserId\n ? [{ userId: form.assigneeUserId, name: form.assigneeName ?? form.assigneeUserId, isCustomer: false }]\n : []}\n onChange={(entries) => {\n const next = entries[entries.length - 1] ?? null\n update({ assigneeUserId: next?.userId ?? null, assigneeName: next?.name ?? null })\n }}\n />\n </Field>\n ) : null}\n </div>\n <div className=\"flex w-full flex-col gap-4\">\n <Field label={t('customers.calendar.editor.relatedTo', 'Related to')} error={errors.relatedTo}>\n <RelatedToField\n label={t('customers.calendar.editor.relatedTo', 'Related to')}\n value={form.relatedTo}\n deal={form.dealId && form.dealLabel ? { id: form.dealId, label: form.dealLabel } : null}\n onChange={(relatedTo) => update({ relatedTo })}\n onDealChange={(deal) => update({ dealId: deal?.id ?? null, dealLabel: deal?.label ?? null })}\n error={errors.relatedTo}\n />\n </Field>\n {config.location ? (\n <LocationField\n variant={config.location}\n value={form.location}\n onChange={(location) => update({ location })}\n phoneContactIds={phoneContactIds}\n />\n ) : null}\n {resourcesEnabled ? (\n <Field label={t('customers.calendar.editor.resources', 'Resources')}>\n <ResourcesField\n placeholder={t('customers.calendar.editor.resourcesPlaceholder', 'Add a resource\u2026')}\n ariaLabel={t('customers.calendar.editor.resources', 'Resources')}\n value={form.resources}\n onChange={(resources) => update({ resources })}\n />\n </Field>\n ) : null}\n {config.hasPriority ? (\n <Field label={t('customers.calendar.editor.priority.label', 'Priority')}>\n <PriorityField\n value={form.priority}\n ariaLabel={t('customers.calendar.editor.priority.label', 'Priority')}\n labels={{\n low: t('customers.calendar.editor.priority.low', 'Low'),\n medium: t('customers.calendar.editor.priority.medium', 'Medium'),\n high: t('customers.calendar.editor.priority.high', 'High'),\n }}\n onChange={(priority) => update({ priority })}\n />\n </Field>\n ) : null}\n </div>\n <Field label={t('customers.calendar.editor.description', 'Description')} className=\"lg:col-span-2\">\n <Textarea\n value={form.description}\n onChange={(event) => update({ description: event.target.value })}\n placeholder={t('customers.calendar.editor.descriptionPlaceholder', 'Add details\u2026')}\n aria-label={t('customers.calendar.editor.description', 'Description')}\n className=\"h-20 resize-none\"\n />\n </Field>\n </div>\n )\n}\n\nexport function CalendarEventEditor({\n open,\n mode,\n item,\n defaultDate,\n defaultRange,\n typeLabels,\n typeIcons,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n onOpenChange,\n onSaved,\n}: CalendarEventEditorProps) {\n const t = useT()\n const [saving, setSaving] = React.useState(false)\n const isEdit = mode === 'edit' && Boolean(item?.id)\n\n // CrudForm reads initialValues once, so every dialog open gets a fresh form\n // instance keyed by the open sequence + edited record.\n const [openSeq, setOpenSeq] = React.useState(0)\n const wasOpenRef = React.useRef(false)\n React.useEffect(() => {\n if (open && !wasOpenRef.current) setOpenSeq((seq) => seq + 1)\n wasOpenRef.current = open\n }, [open])\n const formKey = `${mode}:${item?.id ?? 'new'}:${openSeq}`\n\n const initialValues = React.useMemo<Record<string, unknown>>(() => {\n if (isEdit && item) {\n return {\n ...parseItemToFormState(item),\n ...customFieldInitialValues(item),\n id: item.id,\n updatedAt: item.updatedAt ?? undefined,\n }\n }\n return { ...createDefaultFormState(defaultDate ?? null, undefined, defaultRange ?? null) }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- openSeq re-seeds defaults per dialog open\n }, [isEdit, item, defaultDate, defaultRange, openSeq])\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const form = formStateOfValues(values)\n const config = KIND_CONFIG[form.kind]\n const fieldErrors: Record<string, string> = {}\n if (!form.title.trim()) {\n fieldErrors.title = t('customers.calendar.editor.validation.titleRequired', 'Title is required')\n }\n if (!form.relatedTo) {\n fieldErrors.relatedTo = t('customers.calendar.editor.validation.relatedToRequired', 'Select a person or company to link this event')\n }\n if (config.hasEnd && !form.allDay && computeDurationMinutes(form) === null) {\n fieldErrors.ends = t('customers.calendar.editor.validation.endsBeforeStarts', 'End must be after start')\n }\n // A task must be assigned to a team member \u2014 surface the requirement inline\n // instead of letting the save fail with no visible reason (#3747 feedback).\n if (config.people === 'assignee' && staffEnabled !== false && !form.assigneeUserId) {\n fieldErrors.assignee = t('customers.calendar.editor.validation.assigneeRequired', 'Assign this task to a team member')\n }\n if (Object.keys(fieldErrors).length > 0) {\n throw createCrudFormError(Object.values(fieldErrors)[0], fieldErrors)\n }\n setSaving(true)\n try {\n const payload = buildInteractionPayload(form, {\n mode,\n id: item?.id,\n resourcesEnabled: resourcesEnabled === true,\n staffEnabled: staffEnabled !== false,\n })\n const custom = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n for (const [key, value] of Object.entries(custom)) payload[`cf_${key}`] = value\n // CrudForm supplies the optimistic-lock header (auto-derived from\n // initialValues.updatedAt) via scoped request headers around onSubmit.\n await apiCallOrThrow('/api/customers/interactions', {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n flash(t('customers.calendar.editor.saved', 'Event saved'), 'success')\n onOpenChange(false)\n requestAnimationFrame(() => { onSaved() })\n } catch (err) {\n // Surface an optimistic-lock 409 as the persistent conflict bar and\n // close \u2014 the bar renders at page level, behind the dialog overlay.\n if (extractOptimisticLockConflict(err)) {\n surfaceRecordConflict(err, t)\n onOpenChange(false)\n return\n }\n throw err\n } finally {\n setSaving(false)\n }\n },\n [isEdit, item?.id, mode, onOpenChange, onSaved, resourcesEnabled, staffEnabled, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'details',\n bare: true,\n component: (ctx) => (\n <EditorBody\n ctx={ctx}\n open={open}\n isEdit={isEdit}\n item={item}\n typeLabels={typeLabels}\n typeIcons={typeIcons ?? {}}\n conflictScope={conflictScope ?? 'all'}\n currentUserId={currentUserId ?? null}\n resourcesEnabled={resourcesEnabled === true}\n staffEnabled={staffEnabled !== false}\n />\n ),\n },\n { id: 'customFields', kind: 'customFields' },\n ],\n [open, isEdit, item, typeLabels, typeIcons, conflictScope, currentUserId, resourcesEnabled, staffEnabled],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm: () => {\n const formElement = document.getElementById(FORM_ID)\n if (formElement instanceof HTMLFormElement) formElement.requestSubmit()\n },\n disabled: saving,\n })\n\n const dialogTitle = isEdit ? t('customers.calendar.editor.title.edit', 'Edit event') : t('customers.calendar.editor.title.create', 'New event')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n onKeyDown={handleKeyDown}\n // Only dismiss on a genuine click OUTSIDE the whole editor. Radix\n // relays a click inside the dialog (but outside an open DS\n // DatePicker/Select popover) up to the dialog as an \"interact\n // outside\" event when it dismisses that popover \u2014 without this guard\n // it would tear down the entire editor. Keep it open when the target\n // is inside the dialog body or inside any portalled popover.\n onInteractOutside={(event) => {\n const target = event.detail.originalEvent.target as HTMLElement | null\n if (target?.closest('[data-dialog-content]') || target?.closest('[data-radix-popper-content-wrapper]')) {\n event.preventDefault()\n }\n }}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex h-dvh max-h-dvh w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-xl sm:h-auto sm:max-h-[calc(100dvh-4rem)] sm:w-full sm:max-w-lg sm:rounded-2xl sm:border-0 lg:max-w-3xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{dialogTitle}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-center gap-3 border-b border-border bg-background py-4 pl-5 pr-4\">\n <Calendar aria-hidden className=\"size-6 shrink-0 text-foreground\" strokeWidth={1.75} />\n <p className=\"min-w-0 flex-1 text-sm font-medium leading-5 text-foreground\">{dialogTitle}</p>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n aria-label={t('customers.calendar.editor.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n <div\n className=\"flex-1 overflow-y-auto\"\n onScroll={() => {\n // Tell the DS date/time fields to close their (controlled) popover\n // so a portalled popover doesn't float over the form or drift away\n // from its field while scrolling (#3747 feedback). Radix ignores\n // synthetic dismiss events, so the fields drive their own `open`.\n document.dispatchEvent(new CustomEvent(EDITOR_SCROLL_EVENT))\n }}\n >\n <div className=\"px-4 py-4 sm:px-6 sm:py-5\">\n <CrudForm<Record<string, unknown>>\n key={formKey}\n formId={FORM_ID}\n embedded\n hideFooterActions\n customFieldsManageMode=\"page\"\n fields={[]}\n groups={groups}\n initialValues={initialValues}\n entityIds={INTERACTION_ENTITY_IDS}\n onSubmit={handleSubmit}\n />\n </div>\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-3 border-t border-border bg-background px-5 py-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n {t('customers.calendar.editor.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={saving}\n >\n {saving ? t('customers.calendar.editor.saving', 'Saving\u2026') : t('customers.calendar.editor.save', 'Save event')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { CrudForm, type CrudFormGroup, type CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n buildEditorTypeOptions,\n buildInteractionPayload,\n computeDurationMinutes,\n createDefaultFormState,\n defaultRepeatDaysForDateInput,\n editorKindOfInteractionType,\n KIND_CONFIG,\n parseItemToFormState,\n resolveSavedOwnerUserId,\n type EditorFormState,\n type EditorKind,\n type EditorPriority,\n} from '../../lib/calendar/editorPayload'\nimport type { ConflictScope } from '../../lib/calendar/preferences'\nimport { renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { normalizeCustomFieldSubmitValue } from '../detail/customFieldUtils'\nimport type { CalendarItem } from './types'\nimport { EDITOR_SCROLL_EVENT, Field } from './editor/inputs'\nimport { SegmentGroup } from './editor/SegmentGroup'\nimport { PriorityField } from './editor/PriorityField'\nimport { RelatedToField } from './editor/RelatedToField'\nimport { RepeatField } from './editor/RepeatField'\nimport { PeopleField } from './editor/PeopleField'\nimport { ResourcesField } from './editor/ResourcesField'\nimport { ScheduleSection } from './editor/ScheduleSection'\nimport { LocationField } from './editor/LocationField'\nimport { useConflictProbe, useEditorLabelResolution } from './editor/hooks'\n\nexport interface CalendarEventEditorProps {\n open: boolean\n mode: 'create' | 'edit'\n item?: CalendarItem | null\n defaultDate?: Date\n defaultRange?: { start: Date; end: Date } | null\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n conflictScope?: ConflictScope\n currentUserId?: string | null\n resourcesEnabled?: boolean\n staffEnabled?: boolean\n onOpenChange(open: boolean): void\n onSaved(): void\n}\n\nconst FORM_ID = 'customers-calendar-event-editor'\nconst INTERACTION_ENTITY_IDS = [E.customers.customer_interaction]\n\nconst PEOPLE_FIELD_TEXT = {\n attendees: { labelKey: 'customers.calendar.editor.attendees', label: 'Attendees', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer\u2026' },\n participants: { labelKey: 'customers.calendar.editor.participants', label: 'Participants', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer\u2026' },\n to: { labelKey: 'customers.calendar.editor.to', label: 'To', placeholderKey: 'customers.calendar.editor.addRecipientPlaceholder', placeholder: 'Add recipient\u2026' },\n} as const\n\n// CrudForm values carry the flattened EditorFormState keys (seeded via\n// initialValues) plus cf_* custom-field keys managed by CrudForm itself.\nfunction formStateOfValues(values: Record<string, unknown>): EditorFormState {\n return values as unknown as EditorFormState\n}\n\nfunction customFieldInitialValues(item: CalendarItem): Record<string, unknown> {\n const customValues = (item.raw as Record<string, unknown>).customValues\n if (!customValues || typeof customValues !== 'object' || Array.isArray(customValues)) return {}\n return Object.fromEntries(\n Object.entries(customValues as Record<string, unknown>).map(([key, value]) => [`cf_${key}`, value]),\n )\n}\n\ntype EditorBodyProps = {\n ctx: CrudFormGroupComponentProps\n open: boolean\n isEdit: boolean\n item?: CalendarItem | null\n typeLabels: Record<string, string>\n typeIcons: Record<string, string | null>\n conflictScope: ConflictScope\n currentUserId: string | null\n resourcesEnabled: boolean\n staffEnabled: boolean\n}\n\nfunction EditorBody({\n ctx,\n open,\n isEdit,\n item,\n typeLabels,\n typeIcons,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n}: EditorBodyProps) {\n const t = useT()\n const locale = useLocale()\n const { setValue, errors } = ctx\n const form = formStateOfValues(ctx.values)\n const config = KIND_CONFIG[form.kind]\n\n const update = React.useCallback(\n (patch: Partial<EditorFormState>) => {\n for (const [key, value] of Object.entries(patch)) setValue(key, value)\n },\n [setValue],\n )\n\n useEditorLabelResolution(open, form, update)\n // Probe against the owner the interaction will actually be SAVED with so the\n // editor warning matches the grid's post-save conflict rings: tasks own via\n // their assignee, other kinds stay ownerless on create / keep the existing\n // owner on edit (conflicts then come from shared participants).\n const draftOwnerUserId = resolveSavedOwnerUserId(config, form, isEdit, item?.ownerUserId ?? null)\n // Self-exclude by the underlying interaction id (raw.id): findEditorConflictItems\n // drops candidates by raw.id, and every expanded occurrence of a recurring series\n // shares it \u2014 so the edited record never conflicts with itself.\n const conflict = useConflictProbe(open, form, config, isEdit && item ? item.raw.id : null, draftOwnerUserId, conflictScope, currentUserId)\n\n const kindLabels = React.useMemo(\n () => ({\n meeting: t('customers.calendar.editor.types.meeting', 'meeting'),\n call: t('customers.calendar.editor.types.call', 'call'),\n email: t('customers.calendar.editor.types.email', 'email'),\n note: t('customers.calendar.editor.types.note', 'note'),\n event: t('customers.calendar.editor.types.event', 'event'),\n task: t('customers.calendar.editor.types.task', 'task'),\n }),\n [t],\n ) satisfies Record<EditorKind, string>\n\n const selectedType = form.category ?? form.kind\n const typeOptions = React.useMemo(\n () => buildEditorTypeOptions({ typeLabels, typeIcons, selectedValue: selectedType, kindLabels }),\n [typeLabels, typeIcons, selectedType, kindLabels],\n )\n const typeSwitcherOptions = React.useMemo(\n () =>\n typeOptions.map((option) => ({\n value: option.value,\n label: option.label,\n icon: renderDictionaryIcon(option.icon, 'h-4 w-4'),\n })),\n [typeOptions],\n )\n\n // Candidates for the Call \"insert phone from contact\" button: the linked\n // person (companies have no phone) + customer attendees. Their userId is the\n // person id, so the picker can resolve primary_phone from the people API.\n const phoneContactIds = React.useMemo(() => {\n const ids: string[] = []\n if (form.relatedTo && form.relatedTo.kind !== 'company') ids.push(form.relatedTo.id)\n for (const participant of form.participants) {\n if (participant.isCustomer && participant.userId) ids.push(participant.userId)\n }\n return Array.from(new Set(ids))\n }, [form.relatedTo, form.participants])\n\n const titleLabel = form.kind === 'email'\n ? t('customers.calendar.editor.titleLabel.email', 'Subject')\n : form.kind === 'note'\n ? t('customers.calendar.editor.titleLabel.note', 'Note')\n : t('customers.calendar.editor.titleLabel.generic', 'Title')\n\n return (\n // Single column on phones (full-screen sheet). On lg+ the dialog widens and\n // the fields group into two thematic columns: WHEN (schedule + repeat) on\n // the left, CONTEXT (related record, category, location) on the right;\n // people/resources and the task fields pair up below; title, description\n // and the type switcher span both columns.\n <div className=\"grid w-full grid-cols-1 items-start gap-4 lg:grid-cols-2 lg:gap-x-6\">\n {conflict ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertTitle>{t('customers.calendar.editor.conflictTitle', 'Calendar conflict')}</AlertTitle>\n <AlertDescription>{conflict}</AlertDescription>\n </Alert>\n ) : null}\n <div className=\"w-full lg:col-span-2\">\n <SegmentGroup<string>\n ariaLabel={t('customers.calendar.editor.typeSwitcher', 'Event type')}\n value={selectedType}\n onChange={(type) => update({ kind: editorKindOfInteractionType(type), category: type })}\n options={typeSwitcherOptions}\n />\n </div>\n <Field label={titleLabel} error={errors.title} className=\"lg:col-span-2\">\n <Input\n type=\"text\"\n value={form.title}\n onChange={(event) => update({ title: event.target.value })}\n placeholder={t('customers.calendar.editor.titlePlaceholder', 'Add a title\u2026')}\n aria-label={titleLabel}\n autoFocus\n size=\"lg\"\n />\n </Field>\n <div className=\"flex w-full flex-col gap-4\">\n <ScheduleSection\n dateLabel={config.dateLabel}\n hasAllDay={config.hasAllDay}\n hasEnd={config.hasEnd}\n allDay={form.allDay}\n date={form.date}\n startTime={form.startTime}\n endDate={form.endDate}\n endTime={form.endTime}\n locale={locale}\n endsError={errors.ends}\n onAllDayChange={(allDay) => update({ allDay })}\n onDateChange={(date) => {\n const untouchedDefault =\n JSON.stringify(form.repeatDays) === JSON.stringify(defaultRepeatDaysForDateInput(form.date))\n update(\n untouchedDefault\n ? { date, repeatDays: defaultRepeatDaysForDateInput(date) }\n : { date },\n )\n }}\n onStartTimeChange={(startTime) => update({ startTime })}\n onEndDateChange={(endDate) => update({ endDate })}\n onEndTimeChange={(endTime) => update({ endTime })}\n />\n {config.hasRepeat ? (\n <RepeatField\n freq={form.repeatFreq}\n days={form.repeatDays}\n endType={form.repeatEndType}\n count={form.repeatCount}\n untilDate={form.repeatUntilDate}\n locale={locale}\n onFreqChange={(repeatFreq) =>\n update(\n repeatFreq === 'weekly'\n ? { repeatFreq, repeatDays: defaultRepeatDaysForDateInput(form.date) }\n : { repeatFreq },\n )}\n onToggleDay={(index) =>\n update({ repeatDays: form.repeatDays.map((active, dayIndex) => (dayIndex === index ? !active : active)) })}\n onEndTypeChange={(repeatEndType) => update({ repeatEndType })}\n onCountChange={(repeatCount) => update({ repeatCount })}\n onUntilDateChange={(repeatUntilDate) => update({ repeatUntilDate })}\n />\n ) : null}\n {config.people && config.people !== 'assignee' ? (\n <Field label={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}>\n <PeopleField\n mode=\"multi\"\n includeCustomers\n includeStaff={staffEnabled}\n placeholder={t(PEOPLE_FIELD_TEXT[config.people].placeholderKey, PEOPLE_FIELD_TEXT[config.people].placeholder)}\n ariaLabel={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}\n value={form.participants}\n onChange={(participants) => update({ participants })}\n />\n </Field>\n ) : null}\n {config.people === 'assignee' && staffEnabled ? (\n <Field label={t('customers.calendar.editor.assignee', 'Assignee')} error={errors.assignee}>\n <PeopleField\n mode=\"single\"\n includeCustomers={false}\n includeStaff\n placeholder={t('customers.calendar.editor.assigneePlaceholder', 'Assign to a team member\u2026')}\n ariaLabel={t('customers.calendar.editor.assignee', 'Assignee')}\n value={form.assigneeUserId\n ? [{ userId: form.assigneeUserId, name: form.assigneeName ?? form.assigneeUserId, isCustomer: false }]\n : []}\n onChange={(entries) => {\n const next = entries[entries.length - 1] ?? null\n update({ assigneeUserId: next?.userId ?? null, assigneeName: next?.name ?? null })\n }}\n />\n </Field>\n ) : null}\n </div>\n <div className=\"flex w-full flex-col gap-4\">\n <Field label={t('customers.calendar.editor.relatedTo', 'Related to')} error={errors.relatedTo}>\n <RelatedToField\n label={t('customers.calendar.editor.relatedTo', 'Related to')}\n value={form.relatedTo}\n deal={form.dealId && form.dealLabel ? { id: form.dealId, label: form.dealLabel } : null}\n onChange={(relatedTo) => update({ relatedTo })}\n onDealChange={(deal) => update({ dealId: deal?.id ?? null, dealLabel: deal?.label ?? null })}\n error={errors.relatedTo}\n />\n </Field>\n {config.location ? (\n <LocationField\n variant={config.location}\n value={form.location}\n onChange={(location) => update({ location })}\n phoneContactIds={phoneContactIds}\n />\n ) : null}\n {resourcesEnabled ? (\n <Field label={t('customers.calendar.editor.resources', 'Resources')}>\n <ResourcesField\n placeholder={t('customers.calendar.editor.resourcesPlaceholder', 'Add a resource\u2026')}\n ariaLabel={t('customers.calendar.editor.resources', 'Resources')}\n value={form.resources}\n onChange={(resources) => update({ resources })}\n />\n </Field>\n ) : null}\n {config.hasPriority ? (\n <Field label={t('customers.calendar.editor.priority.label', 'Priority')}>\n <PriorityField\n value={form.priority}\n ariaLabel={t('customers.calendar.editor.priority.label', 'Priority')}\n labels={{\n low: t('customers.calendar.editor.priority.low', 'Low'),\n medium: t('customers.calendar.editor.priority.medium', 'Medium'),\n high: t('customers.calendar.editor.priority.high', 'High'),\n }}\n onChange={(priority) => update({ priority })}\n />\n </Field>\n ) : null}\n </div>\n <Field label={t('customers.calendar.editor.description', 'Description')} className=\"lg:col-span-2\">\n <Textarea\n value={form.description}\n onChange={(event) => update({ description: event.target.value })}\n placeholder={t('customers.calendar.editor.descriptionPlaceholder', 'Add details\u2026')}\n aria-label={t('customers.calendar.editor.description', 'Description')}\n className=\"h-20 resize-none\"\n />\n </Field>\n </div>\n )\n}\n\nexport function CalendarEventEditor({\n open,\n mode,\n item,\n defaultDate,\n defaultRange,\n typeLabels,\n typeIcons,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n onOpenChange,\n onSaved,\n}: CalendarEventEditorProps) {\n const t = useT()\n const [saving, setSaving] = React.useState(false)\n const isEdit = mode === 'edit' && Boolean(item?.id)\n\n // CrudForm reads initialValues once, so every dialog open gets a fresh form\n // instance keyed by the open sequence + edited record.\n const [openSeq, setOpenSeq] = React.useState(0)\n const wasOpenRef = React.useRef(false)\n React.useEffect(() => {\n if (open && !wasOpenRef.current) setOpenSeq((seq) => seq + 1)\n wasOpenRef.current = open\n }, [open])\n const formKey = `${mode}:${item?.id ?? 'new'}:${openSeq}`\n\n const initialValues = React.useMemo<Record<string, unknown>>(() => {\n if (isEdit && item) {\n return {\n ...parseItemToFormState(item),\n ...customFieldInitialValues(item),\n id: item.id,\n updatedAt: item.updatedAt ?? undefined,\n }\n }\n return { ...createDefaultFormState(defaultDate ?? null, undefined, defaultRange ?? null) }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- openSeq re-seeds defaults per dialog open\n }, [isEdit, item, defaultDate, defaultRange, openSeq])\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const form = formStateOfValues(values)\n const config = KIND_CONFIG[form.kind]\n const fieldErrors: Record<string, string> = {}\n if (!form.title.trim()) {\n fieldErrors.title = t('customers.calendar.editor.validation.titleRequired', 'Title is required')\n }\n if (!form.relatedTo) {\n fieldErrors.relatedTo = t('customers.calendar.editor.validation.relatedToRequired', 'Select a person or company to link this event')\n }\n if (config.hasEnd && !form.allDay && computeDurationMinutes(form) === null) {\n fieldErrors.ends = t('customers.calendar.editor.validation.endsBeforeStarts', 'End must be after start')\n }\n // A task must be assigned to a team member \u2014 surface the requirement inline\n // instead of letting the save fail with no visible reason (#3747 feedback).\n if (config.people === 'assignee' && staffEnabled !== false && !form.assigneeUserId) {\n fieldErrors.assignee = t('customers.calendar.editor.validation.assigneeRequired', 'Assign this task to a team member')\n }\n if (Object.keys(fieldErrors).length > 0) {\n throw createCrudFormError(Object.values(fieldErrors)[0], fieldErrors)\n }\n setSaving(true)\n try {\n const payload = buildInteractionPayload(form, {\n mode,\n id: item?.id,\n resourcesEnabled: resourcesEnabled === true,\n staffEnabled: staffEnabled !== false,\n })\n const custom = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n for (const [key, value] of Object.entries(custom)) payload[`cf_${key}`] = value\n // CrudForm supplies the optimistic-lock header (auto-derived from\n // initialValues.updatedAt) via scoped request headers around onSubmit.\n await apiCallOrThrow('/api/customers/interactions', {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n flash(t('customers.calendar.editor.saved', 'Event saved'), 'success')\n onOpenChange(false)\n requestAnimationFrame(() => { onSaved() })\n } catch (err) {\n // Surface an optimistic-lock 409 as the persistent conflict bar and\n // close \u2014 the bar renders at page level, behind the dialog overlay.\n if (extractOptimisticLockConflict(err)) {\n surfaceRecordConflict(err, t)\n onOpenChange(false)\n return\n }\n throw err\n } finally {\n setSaving(false)\n }\n },\n [isEdit, item?.id, mode, onOpenChange, onSaved, resourcesEnabled, staffEnabled, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'details',\n bare: true,\n component: (ctx) => (\n <EditorBody\n ctx={ctx}\n open={open}\n isEdit={isEdit}\n item={item}\n typeLabels={typeLabels}\n typeIcons={typeIcons ?? {}}\n conflictScope={conflictScope ?? 'all'}\n currentUserId={currentUserId ?? null}\n resourcesEnabled={resourcesEnabled === true}\n staffEnabled={staffEnabled !== false}\n />\n ),\n },\n { id: 'customFields', kind: 'customFields' },\n ],\n [open, isEdit, item, typeLabels, typeIcons, conflictScope, currentUserId, resourcesEnabled, staffEnabled],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm: () => {\n const formElement = document.getElementById(FORM_ID)\n if (formElement instanceof HTMLFormElement) formElement.requestSubmit()\n },\n disabled: saving,\n })\n\n const dialogTitle = isEdit ? t('customers.calendar.editor.title.edit', 'Edit event') : t('customers.calendar.editor.title.create', 'New event')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n onKeyDown={handleKeyDown}\n // Only dismiss on a genuine click OUTSIDE the whole editor. Radix\n // relays a click inside the dialog (but outside an open DS\n // DatePicker/Select popover) up to the dialog as an \"interact\n // outside\" event when it dismisses that popover \u2014 without this guard\n // it would tear down the entire editor. Keep it open when the target\n // is inside the dialog body or inside any portalled popover.\n onInteractOutside={(event) => {\n const target = event.detail.originalEvent.target as HTMLElement | null\n if (target?.closest('[data-dialog-content]') || target?.closest('[data-radix-popper-content-wrapper]')) {\n event.preventDefault()\n }\n }}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex h-dvh max-h-dvh w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-xl sm:h-auto sm:max-h-[calc(100dvh-4rem)] sm:w-full sm:max-w-lg sm:rounded-2xl sm:border-0 lg:max-w-3xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{dialogTitle}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-center gap-3 border-b border-border bg-background py-4 pl-5 pr-4\">\n <Calendar aria-hidden className=\"size-6 shrink-0 text-foreground\" strokeWidth={1.75} />\n <p className=\"min-w-0 flex-1 text-sm font-medium leading-5 text-foreground\">{dialogTitle}</p>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n aria-label={t('customers.calendar.editor.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n <div\n className=\"flex-1 overflow-y-auto\"\n onScroll={() => {\n // Tell the DS date/time fields to close their (controlled) popover\n // so a portalled popover doesn't float over the form or drift away\n // from its field while scrolling (#3747 feedback). Radix ignores\n // synthetic dismiss events, so the fields drive their own `open`.\n document.dispatchEvent(new CustomEvent(EDITOR_SCROLL_EVENT))\n }}\n >\n <div className=\"px-4 py-4 sm:px-6 sm:py-5\">\n <CrudForm<Record<string, unknown>>\n key={formKey}\n formId={FORM_ID}\n embedded\n hideFooterActions\n customFieldsManageMode=\"page\"\n fields={[]}\n groups={groups}\n initialValues={initialValues}\n entityIds={INTERACTION_ENTITY_IDS}\n onSubmit={handleSubmit}\n />\n </div>\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-3 border-t border-border bg-background px-5 py-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n {t('customers.calendar.editor.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={saving}\n >\n {saving ? t('customers.calendar.editor.saving', 'Saving\u2026') : t('customers.calendar.editor.save', 'Save event')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -181,7 +181,7 @@ function CalendarScreen({ resourcesEnabled = false, staffEnabled = true } = {})
181
181
  const participantNames = /* @__PURE__ */ new Map();
182
182
  for (const item of visibleItems) {
183
183
  for (const participant of item.participants) {
184
- if (participant.name && !participantNames.has(participant.userId)) {
184
+ if (participant.userId && participant.name && !participantNames.has(participant.userId)) {
185
185
  participantNames.set(participant.userId, participant.name);
186
186
  }
187
187
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/CalendarScreen.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { matchFeature } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { countByCategory } from '../../lib/calendar/categories'\nimport { findConflicts } from '../../lib/calendar/conflicts'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { AgendaList } from './AgendaList'\nimport { CalendarFooter } from './CalendarFooter'\nimport { CalendarHeader } from './CalendarHeader'\nimport { CalendarTabs } from './CalendarTabs'\nimport { CalendarToolbar } from './CalendarToolbar'\nimport { MonthGrid } from './MonthGrid'\nimport { ShortcutsDialog } from './ShortcutsDialog'\nimport { TimeGrid } from './TimeGrid'\nimport { UpcomingCards } from './UpcomingCards'\nimport { CalendarSettingsModal } from './CalendarSettingsModal'\nimport { useCalendarPreferences } from './useCalendarPreferences'\nimport { MAX_WINDOW_ITEMS, useCalendarItems } from './useCalendarItems'\nimport type {\n CalendarFiltersValue,\n CalendarItem,\n CalendarRangePreset,\n CalendarTab,\n CalendarView,\n UpcomingCard,\n} from './types'\n\nconst CalendarEventEditor = dynamic(\n () => import('./CalendarEventEditor').then((mod) => mod.CalendarEventEditor),\n { ssr: false },\n)\n\nconst SEARCH_DEBOUNCE_MS = 200\nconst PHONE_BREAKPOINT_PX = 640\nconst HIGHLIGHT_CLEAR_MS = 3000\nconst DEFAULT_AGENDA_HORIZON_DAYS = 7\nconst UPCOMING_CARDS_COUNT = 4\nconst EMPTY_FILTERS: CalendarFiltersValue = { types: [], status: null, ownerUserId: null }\n\ntype EditorState = { open: boolean; mode: 'create' | 'edit'; item: CalendarItem | null }\n\nconst MANAGE_FEATURE = 'customers.interactions.manage'\n\nfunction useCanManageInteractions(): boolean {\n const [canManage, setCanManage] = React.useState(false)\n React.useEffect(() => {\n const controller = new AbortController()\n let cancelled = false\n apiCall<{ granted?: unknown[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n body: JSON.stringify({ features: [MANAGE_FEATURE] }),\n })\n .then((call) => {\n if (cancelled || !call.ok) return\n const granted = Array.isArray(call.result?.granted)\n ? call.result.granted.map((feature) => String(feature))\n : []\n setCanManage(granted.some((grantedFeature) => matchFeature(MANAGE_FEATURE, grantedFeature)))\n })\n .catch(() => {\n if (!cancelled && !controller.signal.aborted) setCanManage(false)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [])\n return canManage\n}\n\nfunction buildTimezoneLabel(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n const offsetMinutes = -new Date().getTimezoneOffset()\n const sign = offsetMinutes >= 0 ? '+' : '-'\n const absolute = Math.abs(offsetMinutes)\n const hours = Math.floor(absolute / 60)\n const minutes = absolute % 60\n const offset = minutes > 0 ? `${sign}${hours}:${String(minutes).padStart(2, '0')}` : `${sign}${hours}`\n return `${timeZone} (GMT${offset})`\n}\n\nfunction resolveJoinUrl(location: string | null): string | null {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n}\n\nfunction asEditableItem(item: CalendarItem): CalendarItem {\n return item.isRecurringOccurrence ? { ...item, id: item.raw.id } : item\n}\n\nexport type CalendarScreenProps = {\n /** True when the optional resources module is loaded (server-resolved). */\n resourcesEnabled?: boolean\n /** True when the optional staff module is loaded (server-resolved). */\n staffEnabled?: boolean\n}\n\nexport function CalendarScreen({ resourcesEnabled = false, staffEnabled = true }: CalendarScreenProps = {}) {\n const t = useT()\n const [view, setView] = React.useState<CalendarView>('week')\n const [anchor, setAnchor] = React.useState<Date>(() => new Date())\n const [agendaHorizonDays, setAgendaHorizonDays] = React.useState(DEFAULT_AGENDA_HORIZON_DAYS)\n const [preset, setPreset] = React.useState<CalendarRangePreset | null>('thisWeek')\n\n React.useEffect(() => {\n if (window.innerWidth >= PHONE_BREAKPOINT_PX) return\n setView('day')\n setPreset(null)\n }, [])\n const [tab, setTab] = React.useState<CalendarTab>('all')\n const [searchText, setSearchText] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filters, setFilters] = React.useState<CalendarFiltersValue>(EMPTY_FILTERS)\n const [editor, setEditor] = React.useState<EditorState>({ open: false, mode: 'create', item: null })\n const [editorMounted, setEditorMounted] = React.useState(false)\n const [createRange, setCreateRange] = React.useState<{ start: Date; end: Date } | null>(null)\n const [shortcutsOpen, setShortcutsOpen] = React.useState(false)\n const [settingsOpen, setSettingsOpen] = React.useState(false)\n const [highlightItemId, setHighlightItemId] = React.useState<string | null>(null)\n const [hasLoadedOnce, setHasLoadedOnce] = React.useState(false)\n const { preferences, setPreferences, hydrated: preferencesHydrated, userId: currentUserId } = useCalendarPreferences()\n\n const range = React.useMemo(\n () => getVisibleRange(view, anchor, agendaHorizonDays),\n [view, anchor, agendaHorizonDays],\n )\n const { items, isLoading, error, truncated, typeLabels, typeColors, typeIcons, refetch } = useCalendarItems(range)\n\n React.useEffect(() => {\n if (!isLoading) setHasLoadedOnce(true)\n }, [isLoading])\n\n React.useEffect(() => {\n const timer = window.setTimeout(() => setDebouncedSearch(searchText), SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [searchText])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const timer = window.setTimeout(() => setHighlightItemId(null), HIGHLIGHT_CLEAR_MS)\n return () => window.clearTimeout(timer)\n }, [highlightItemId])\n\n const visibleItems = React.useMemo(\n () => items.filter((item) => item.end > range.from && item.start < range.to),\n [items, range],\n )\n\n const searchedItems = React.useMemo(() => {\n const query = debouncedSearch.trim().toLowerCase()\n if (!query) return visibleItems\n return visibleItems.filter((item) => {\n if (item.title.toLowerCase().includes(query)) return true\n if (item.location && item.location.toLowerCase().includes(query)) return true\n const rawBody = (item.raw as { body?: unknown }).body\n if (typeof rawBody === 'string' && rawBody.toLowerCase().includes(query)) return true\n return item.participants.some((participant) =>\n (participant.name ?? '').toLowerCase().includes(query),\n )\n })\n }, [visibleItems, debouncedSearch])\n\n const baseItems = React.useMemo(\n () =>\n searchedItems.filter((item) => {\n if (filters.types.length > 0 && !filters.types.includes(item.interactionType)) return false\n if (filters.status && item.status !== filters.status) return false\n if (filters.ownerUserId && item.ownerUserId !== filters.ownerUserId) return false\n if (!preferences.showCrmActivities && item.category !== 'meeting' && item.category !== 'event') return false\n return true\n }),\n [searchedItems, filters, preferences.showCrmActivities],\n )\n\n const tabCounts = React.useMemo(() => countByCategory(baseItems), [baseItems])\n\n const viewItems = React.useMemo(() => {\n if (tab === 'meetings') return baseItems.filter((item) => item.category === 'meeting')\n if (tab === 'events') return baseItems.filter((item) => item.category === 'event')\n return baseItems\n }, [baseItems, tab])\n\n const conflictMap = React.useMemo(\n () => findConflicts(baseItems, { scope: preferences.conflictScope, currentUserId }),\n [baseItems, preferences.conflictScope, currentUserId],\n )\n const conflictIds = React.useMemo(() => new Set(conflictMap.keys()), [conflictMap])\n\n const upcomingCards = React.useMemo<UpcomingCard[]>(() => {\n const now = new Date()\n const nowMs = now.getTime()\n return baseItems\n .filter((item) => item.start.getTime() >= nowMs)\n .sort((first, second) => first.start.getTime() - second.start.getTime())\n .slice(0, UPCOMING_CARDS_COUNT)\n .map((item) => {\n const conflictCount = preferences.conflictWarnings ? (conflictMap.get(item.id)?.length ?? 0) : 0\n const kind: UpcomingCard['kind'] =\n item.status === 'canceled'\n ? 'cancelled'\n : conflictCount > 0\n ? 'conflicted'\n : isSameDay(item.start, now)\n ? 'today'\n : 'future'\n return { item, kind, conflictCount }\n })\n }, [baseItems, conflictMap, preferences.conflictWarnings])\n\n const typeOptions = React.useMemo(() => {\n const values = new Set<string>(Object.keys(typeLabels))\n for (const item of visibleItems) values.add(item.interactionType)\n return [...values]\n .sort((a, b) => a.localeCompare(b))\n .map((value) => ({ value, label: typeLabels[value] ?? value }))\n }, [visibleItems, typeLabels])\n\n const ownerOptions = React.useMemo(() => {\n const participantNames = new Map<string, string>()\n for (const item of visibleItems) {\n for (const participant of item.participants) {\n if (participant.name && !participantNames.has(participant.userId)) {\n participantNames.set(participant.userId, participant.name)\n }\n }\n }\n const owners = new Map<string, string>()\n for (const item of visibleItems) {\n if (!item.ownerUserId || owners.has(item.ownerUserId)) continue\n owners.set(item.ownerUserId, participantNames.get(item.ownerUserId) ?? item.ownerUserId)\n }\n return [...owners.entries()]\n .map(([value, label]) => ({ value, label }))\n .sort((first, second) => first.label.localeCompare(second.label))\n }, [visibleItems])\n\n const timezoneLabel = React.useMemo(() => buildTimezoneLabel(), [])\n\n const canManage = useCanManageInteractions()\n\n const openCreateEditor = React.useCallback(() => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n }, [canManage])\n\n const openEditEditor = React.useCallback(\n (item: CalendarItem) => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'edit', item: asEditableItem(item) })\n },\n [canManage],\n )\n\n const handleCreateRange = React.useCallback(\n (start: Date, end: Date) => {\n if (!canManage) return\n setCreateRange({ start, end })\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n },\n [canManage],\n )\n\n const seedActivityTypes = React.useMemo(() => {\n const seen = new Set<string>()\n const labels: string[] = []\n for (const value of Object.keys(typeLabels)) {\n const label = typeLabels[value] ?? value\n if (seen.has(label)) continue\n seen.add(label)\n labels.push(label)\n }\n return labels\n }, [typeLabels])\n\n const handleToday = React.useCallback(() => {\n setAnchor(new Date())\n setPreset(null)\n }, [])\n\n const handlePresetChange = React.useCallback((next: CalendarRangePreset) => {\n setPreset(next)\n setAnchor(new Date())\n if (next === 'thisWeek') {\n setView('week')\n } else if (next === 'thisMonth') {\n setView('month')\n } else {\n setView('agenda')\n setAgendaHorizonDays(next === 'next30' ? 30 : DEFAULT_AGENDA_HORIZON_DAYS)\n }\n }, [])\n\n const handleAnchorChange = React.useCallback((date: Date) => {\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleViewChange = React.useCallback((next: CalendarView) => {\n setView(next)\n setPreset(null)\n }, [])\n\n const handleTimeGridNavigate = React.useCallback((deltaDays: number) => {\n setAnchor((current) => addDays(current, deltaDays))\n setPreset(null)\n }, [])\n\n const handleDayOpen = React.useCallback((date: Date) => {\n setView('day')\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleSeeConflict = React.useCallback((item: CalendarItem) => {\n setView('week')\n setAnchor(item.start)\n setPreset(null)\n setHighlightItemId(item.id)\n }, [])\n\n const handleJoin = React.useCallback((item: CalendarItem) => {\n const url = resolveJoinUrl(item.location)\n if (url) window.open(url, '_blank', 'noopener')\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-calendar-cancel',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const handleCancelItem = React.useCallback(\n async (item: CalendarItem) => {\n const interactionId = item.raw.id\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(item.updatedAt), () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: interactionId, status: 'canceled' }),\n }),\n ),\n mutationPayload: {\n operation: 'cancelCalendarEvent',\n interactionId,\n interactionType: item.interactionType,\n },\n context: {\n formId: 'customers-calendar-cancel',\n resourceKind: 'customers.interaction',\n resourceId: interactionId,\n retryLastMutation,\n },\n })\n flash(t('customers.calendar.cards.cancelSuccess', 'Event cancelled'), 'success')\n refetch()\n } catch (err) {\n // An optimistic-lock 409 is surfaced as the persistent conflict bar by\n // useGuardedMutation (surfaceRecordConflict) \u2014 don't re-flash.\n if (extractOptimisticLockConflict(err)) return\n flash(t('customers.calendar.cards.cancelError', 'Failed to cancel event'), 'error')\n }\n },\n [refetch, retryLastMutation, runMutation, t],\n )\n\n const focusSearch = React.useCallback(() => {\n const node = document.querySelector('[data-calendar-search]')\n if (node instanceof HTMLInputElement) node.focus()\n else if (node instanceof HTMLElement) node.querySelector('input')?.focus()\n }, [])\n\n const editorOpen = editor.open\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented) return\n if (event.metaKey || event.ctrlKey || event.altKey) return\n const target = event.target\n if (target instanceof HTMLElement) {\n const tag = target.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) return\n }\n if (editorOpen) return\n switch (event.key) {\n case 't':\n case 'T':\n event.preventDefault()\n setAnchor(new Date())\n setPreset(null)\n break\n case 'd':\n case 'D':\n event.preventDefault()\n setView('day')\n setPreset(null)\n break\n case 'w':\n case 'W':\n event.preventDefault()\n setView('week')\n setPreset(null)\n break\n case 'm':\n case 'M':\n event.preventDefault()\n setView('month')\n setPreset(null)\n break\n case 'a':\n case 'A':\n event.preventDefault()\n setView('agenda')\n setPreset(null)\n break\n case 'n':\n case 'N':\n event.preventDefault()\n openCreateEditor()\n break\n case '/':\n event.preventDefault()\n focusSearch()\n break\n case '?':\n event.preventDefault()\n setShortcutsOpen((open) => !open)\n break\n case 'Escape':\n setHighlightItemId(null)\n break\n default:\n break\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [editorOpen, focusSearch, openCreateEditor])\n\n const showInitialLoading = (isLoading && !hasLoadedOnce) || !preferencesHydrated\n\n let viewArea: React.ReactNode\n if (error) {\n viewArea = (\n <ErrorMessage\n label={t('customers.calendar.errors.loadFailed', 'Failed to load calendar events.')}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={refetch}>\n {t('customers.calendar.errors.retry', 'Retry')}\n </Button>\n }\n />\n )\n } else if (showInitialLoading) {\n viewArea = <LoadingMessage label={t('customers.calendar.loading', 'Loading calendar\u2026')} />\n } else if (view === 'month') {\n viewArea = (\n <MonthGrid anchor={anchor} items={viewItems} onItemClick={openEditEditor} onDayOpen={handleDayOpen} />\n )\n } else if (view === 'agenda') {\n viewArea = (\n <AgendaList\n anchor={anchor}\n horizonDays={agendaHorizonDays}\n items={viewItems}\n typeLabels={typeLabels}\n onItemClick={openEditEditor}\n />\n )\n } else {\n viewArea = (\n <TimeGrid\n days={view === 'day' ? 1 : 7}\n anchor={anchor}\n items={viewItems}\n conflictIds={conflictIds}\n showWeekends={preferences.showWeekends}\n showConflicts={preferences.conflictWarnings}\n aiSummaries={preferences.aiSummaries}\n canManage={canManage}\n highlightItemId={highlightItemId}\n onItemClick={openEditEditor}\n onJoin={handleJoin}\n onNavigate={handleTimeGridNavigate}\n onCreate={canManage ? openCreateEditor : undefined}\n onCreateRange={canManage ? handleCreateRange : undefined}\n />\n )\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col gap-4\">\n <CalendarHeader view={view} anchor={anchor} onNewEvent={canManage ? openCreateEditor : undefined} />\n <CalendarToolbar\n view={view}\n anchor={anchor}\n range={range}\n preset={preset}\n search={searchText}\n filters={filters}\n typeOptions={typeOptions}\n ownerOptions={ownerOptions}\n onToday={handleToday}\n onPresetChange={handlePresetChange}\n onAnchorChange={handleAnchorChange}\n onSearchChange={setSearchText}\n onFiltersChange={setFilters}\n onOpenSettings={() => setSettingsOpen(true)}\n />\n <UpcomingCards\n cards={upcomingCards}\n canManage={canManage}\n onJoin={handleJoin}\n onSeeConflict={handleSeeConflict}\n onOpen={openEditEditor}\n onEdit={openEditEditor}\n onCancel={handleCancelItem}\n />\n <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n <CalendarTabs\n tab={tab}\n counts={tabCounts}\n view={view}\n onTabChange={setTab}\n onViewChange={handleViewChange}\n />\n {truncated ? (\n <p className=\"text-xs text-muted-foreground\" role=\"status\">\n {t('customers.calendar.notice.truncated', 'Showing first {count} items for this range.', {\n count: MAX_WINDOW_ITEMS,\n })}\n </p>\n ) : null}\n <div className=\"flex min-h-[560px] flex-1 flex-col [&>*]:flex-1\">{viewArea}</div>\n </div>\n <div className=\"hidden md:block\">\n <CalendarFooter timezoneLabel={timezoneLabel} onOpenShortcuts={() => setShortcutsOpen(true)} />\n </div>\n <ShortcutsDialog open={shortcutsOpen} onOpenChange={setShortcutsOpen} />\n <CalendarSettingsModal\n open={settingsOpen}\n preferences={preferences}\n seedActivityTypes={seedActivityTypes}\n onOpenChange={setSettingsOpen}\n onSave={(next) => {\n setPreferences(next)\n flash(t('customers.calendar.settings.saved', 'Calendar settings saved'), 'success')\n }}\n />\n {editorMounted ? (\n <CalendarEventEditor\n open={editor.open}\n mode={editor.mode}\n item={editor.item}\n defaultDate={anchor}\n defaultRange={createRange}\n typeLabels={typeLabels}\n typeIcons={typeIcons}\n conflictScope={preferences.conflictScope}\n currentUserId={currentUserId}\n resourcesEnabled={resourcesEnabled}\n staffEnabled={staffEnabled}\n onOpenChange={(open) => setEditor((current) => ({ ...current, open }))}\n onSaved={refetch}\n />\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { matchFeature } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { countByCategory } from '../../lib/calendar/categories'\nimport { findConflicts } from '../../lib/calendar/conflicts'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { AgendaList } from './AgendaList'\nimport { CalendarFooter } from './CalendarFooter'\nimport { CalendarHeader } from './CalendarHeader'\nimport { CalendarTabs } from './CalendarTabs'\nimport { CalendarToolbar } from './CalendarToolbar'\nimport { MonthGrid } from './MonthGrid'\nimport { ShortcutsDialog } from './ShortcutsDialog'\nimport { TimeGrid } from './TimeGrid'\nimport { UpcomingCards } from './UpcomingCards'\nimport { CalendarSettingsModal } from './CalendarSettingsModal'\nimport { useCalendarPreferences } from './useCalendarPreferences'\nimport { MAX_WINDOW_ITEMS, useCalendarItems } from './useCalendarItems'\nimport type {\n CalendarFiltersValue,\n CalendarItem,\n CalendarRangePreset,\n CalendarTab,\n CalendarView,\n UpcomingCard,\n} from './types'\n\nconst CalendarEventEditor = dynamic(\n () => import('./CalendarEventEditor').then((mod) => mod.CalendarEventEditor),\n { ssr: false },\n)\n\nconst SEARCH_DEBOUNCE_MS = 200\nconst PHONE_BREAKPOINT_PX = 640\nconst HIGHLIGHT_CLEAR_MS = 3000\nconst DEFAULT_AGENDA_HORIZON_DAYS = 7\nconst UPCOMING_CARDS_COUNT = 4\nconst EMPTY_FILTERS: CalendarFiltersValue = { types: [], status: null, ownerUserId: null }\n\ntype EditorState = { open: boolean; mode: 'create' | 'edit'; item: CalendarItem | null }\n\nconst MANAGE_FEATURE = 'customers.interactions.manage'\n\nfunction useCanManageInteractions(): boolean {\n const [canManage, setCanManage] = React.useState(false)\n React.useEffect(() => {\n const controller = new AbortController()\n let cancelled = false\n apiCall<{ granted?: unknown[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n body: JSON.stringify({ features: [MANAGE_FEATURE] }),\n })\n .then((call) => {\n if (cancelled || !call.ok) return\n const granted = Array.isArray(call.result?.granted)\n ? call.result.granted.map((feature) => String(feature))\n : []\n setCanManage(granted.some((grantedFeature) => matchFeature(MANAGE_FEATURE, grantedFeature)))\n })\n .catch(() => {\n if (!cancelled && !controller.signal.aborted) setCanManage(false)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [])\n return canManage\n}\n\nfunction buildTimezoneLabel(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n const offsetMinutes = -new Date().getTimezoneOffset()\n const sign = offsetMinutes >= 0 ? '+' : '-'\n const absolute = Math.abs(offsetMinutes)\n const hours = Math.floor(absolute / 60)\n const minutes = absolute % 60\n const offset = minutes > 0 ? `${sign}${hours}:${String(minutes).padStart(2, '0')}` : `${sign}${hours}`\n return `${timeZone} (GMT${offset})`\n}\n\nfunction resolveJoinUrl(location: string | null): string | null {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n}\n\nfunction asEditableItem(item: CalendarItem): CalendarItem {\n return item.isRecurringOccurrence ? { ...item, id: item.raw.id } : item\n}\n\nexport type CalendarScreenProps = {\n /** True when the optional resources module is loaded (server-resolved). */\n resourcesEnabled?: boolean\n /** True when the optional staff module is loaded (server-resolved). */\n staffEnabled?: boolean\n}\n\nexport function CalendarScreen({ resourcesEnabled = false, staffEnabled = true }: CalendarScreenProps = {}) {\n const t = useT()\n const [view, setView] = React.useState<CalendarView>('week')\n const [anchor, setAnchor] = React.useState<Date>(() => new Date())\n const [agendaHorizonDays, setAgendaHorizonDays] = React.useState(DEFAULT_AGENDA_HORIZON_DAYS)\n const [preset, setPreset] = React.useState<CalendarRangePreset | null>('thisWeek')\n\n React.useEffect(() => {\n if (window.innerWidth >= PHONE_BREAKPOINT_PX) return\n setView('day')\n setPreset(null)\n }, [])\n const [tab, setTab] = React.useState<CalendarTab>('all')\n const [searchText, setSearchText] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filters, setFilters] = React.useState<CalendarFiltersValue>(EMPTY_FILTERS)\n const [editor, setEditor] = React.useState<EditorState>({ open: false, mode: 'create', item: null })\n const [editorMounted, setEditorMounted] = React.useState(false)\n const [createRange, setCreateRange] = React.useState<{ start: Date; end: Date } | null>(null)\n const [shortcutsOpen, setShortcutsOpen] = React.useState(false)\n const [settingsOpen, setSettingsOpen] = React.useState(false)\n const [highlightItemId, setHighlightItemId] = React.useState<string | null>(null)\n const [hasLoadedOnce, setHasLoadedOnce] = React.useState(false)\n const { preferences, setPreferences, hydrated: preferencesHydrated, userId: currentUserId } = useCalendarPreferences()\n\n const range = React.useMemo(\n () => getVisibleRange(view, anchor, agendaHorizonDays),\n [view, anchor, agendaHorizonDays],\n )\n const { items, isLoading, error, truncated, typeLabels, typeColors, typeIcons, refetch } = useCalendarItems(range)\n\n React.useEffect(() => {\n if (!isLoading) setHasLoadedOnce(true)\n }, [isLoading])\n\n React.useEffect(() => {\n const timer = window.setTimeout(() => setDebouncedSearch(searchText), SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [searchText])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const timer = window.setTimeout(() => setHighlightItemId(null), HIGHLIGHT_CLEAR_MS)\n return () => window.clearTimeout(timer)\n }, [highlightItemId])\n\n const visibleItems = React.useMemo(\n () => items.filter((item) => item.end > range.from && item.start < range.to),\n [items, range],\n )\n\n const searchedItems = React.useMemo(() => {\n const query = debouncedSearch.trim().toLowerCase()\n if (!query) return visibleItems\n return visibleItems.filter((item) => {\n if (item.title.toLowerCase().includes(query)) return true\n if (item.location && item.location.toLowerCase().includes(query)) return true\n const rawBody = (item.raw as { body?: unknown }).body\n if (typeof rawBody === 'string' && rawBody.toLowerCase().includes(query)) return true\n return item.participants.some((participant) =>\n (participant.name ?? '').toLowerCase().includes(query),\n )\n })\n }, [visibleItems, debouncedSearch])\n\n const baseItems = React.useMemo(\n () =>\n searchedItems.filter((item) => {\n if (filters.types.length > 0 && !filters.types.includes(item.interactionType)) return false\n if (filters.status && item.status !== filters.status) return false\n if (filters.ownerUserId && item.ownerUserId !== filters.ownerUserId) return false\n if (!preferences.showCrmActivities && item.category !== 'meeting' && item.category !== 'event') return false\n return true\n }),\n [searchedItems, filters, preferences.showCrmActivities],\n )\n\n const tabCounts = React.useMemo(() => countByCategory(baseItems), [baseItems])\n\n const viewItems = React.useMemo(() => {\n if (tab === 'meetings') return baseItems.filter((item) => item.category === 'meeting')\n if (tab === 'events') return baseItems.filter((item) => item.category === 'event')\n return baseItems\n }, [baseItems, tab])\n\n const conflictMap = React.useMemo(\n () => findConflicts(baseItems, { scope: preferences.conflictScope, currentUserId }),\n [baseItems, preferences.conflictScope, currentUserId],\n )\n const conflictIds = React.useMemo(() => new Set(conflictMap.keys()), [conflictMap])\n\n const upcomingCards = React.useMemo<UpcomingCard[]>(() => {\n const now = new Date()\n const nowMs = now.getTime()\n return baseItems\n .filter((item) => item.start.getTime() >= nowMs)\n .sort((first, second) => first.start.getTime() - second.start.getTime())\n .slice(0, UPCOMING_CARDS_COUNT)\n .map((item) => {\n const conflictCount = preferences.conflictWarnings ? (conflictMap.get(item.id)?.length ?? 0) : 0\n const kind: UpcomingCard['kind'] =\n item.status === 'canceled'\n ? 'cancelled'\n : conflictCount > 0\n ? 'conflicted'\n : isSameDay(item.start, now)\n ? 'today'\n : 'future'\n return { item, kind, conflictCount }\n })\n }, [baseItems, conflictMap, preferences.conflictWarnings])\n\n const typeOptions = React.useMemo(() => {\n const values = new Set<string>(Object.keys(typeLabels))\n for (const item of visibleItems) values.add(item.interactionType)\n return [...values]\n .sort((a, b) => a.localeCompare(b))\n .map((value) => ({ value, label: typeLabels[value] ?? value }))\n }, [visibleItems, typeLabels])\n\n const ownerOptions = React.useMemo(() => {\n const participantNames = new Map<string, string>()\n for (const item of visibleItems) {\n for (const participant of item.participants) {\n if (participant.userId && participant.name && !participantNames.has(participant.userId)) {\n participantNames.set(participant.userId, participant.name)\n }\n }\n }\n const owners = new Map<string, string>()\n for (const item of visibleItems) {\n if (!item.ownerUserId || owners.has(item.ownerUserId)) continue\n owners.set(item.ownerUserId, participantNames.get(item.ownerUserId) ?? item.ownerUserId)\n }\n return [...owners.entries()]\n .map(([value, label]) => ({ value, label }))\n .sort((first, second) => first.label.localeCompare(second.label))\n }, [visibleItems])\n\n const timezoneLabel = React.useMemo(() => buildTimezoneLabel(), [])\n\n const canManage = useCanManageInteractions()\n\n const openCreateEditor = React.useCallback(() => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n }, [canManage])\n\n const openEditEditor = React.useCallback(\n (item: CalendarItem) => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'edit', item: asEditableItem(item) })\n },\n [canManage],\n )\n\n const handleCreateRange = React.useCallback(\n (start: Date, end: Date) => {\n if (!canManage) return\n setCreateRange({ start, end })\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n },\n [canManage],\n )\n\n const seedActivityTypes = React.useMemo(() => {\n const seen = new Set<string>()\n const labels: string[] = []\n for (const value of Object.keys(typeLabels)) {\n const label = typeLabels[value] ?? value\n if (seen.has(label)) continue\n seen.add(label)\n labels.push(label)\n }\n return labels\n }, [typeLabels])\n\n const handleToday = React.useCallback(() => {\n setAnchor(new Date())\n setPreset(null)\n }, [])\n\n const handlePresetChange = React.useCallback((next: CalendarRangePreset) => {\n setPreset(next)\n setAnchor(new Date())\n if (next === 'thisWeek') {\n setView('week')\n } else if (next === 'thisMonth') {\n setView('month')\n } else {\n setView('agenda')\n setAgendaHorizonDays(next === 'next30' ? 30 : DEFAULT_AGENDA_HORIZON_DAYS)\n }\n }, [])\n\n const handleAnchorChange = React.useCallback((date: Date) => {\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleViewChange = React.useCallback((next: CalendarView) => {\n setView(next)\n setPreset(null)\n }, [])\n\n const handleTimeGridNavigate = React.useCallback((deltaDays: number) => {\n setAnchor((current) => addDays(current, deltaDays))\n setPreset(null)\n }, [])\n\n const handleDayOpen = React.useCallback((date: Date) => {\n setView('day')\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleSeeConflict = React.useCallback((item: CalendarItem) => {\n setView('week')\n setAnchor(item.start)\n setPreset(null)\n setHighlightItemId(item.id)\n }, [])\n\n const handleJoin = React.useCallback((item: CalendarItem) => {\n const url = resolveJoinUrl(item.location)\n if (url) window.open(url, '_blank', 'noopener')\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-calendar-cancel',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const handleCancelItem = React.useCallback(\n async (item: CalendarItem) => {\n const interactionId = item.raw.id\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(item.updatedAt), () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: interactionId, status: 'canceled' }),\n }),\n ),\n mutationPayload: {\n operation: 'cancelCalendarEvent',\n interactionId,\n interactionType: item.interactionType,\n },\n context: {\n formId: 'customers-calendar-cancel',\n resourceKind: 'customers.interaction',\n resourceId: interactionId,\n retryLastMutation,\n },\n })\n flash(t('customers.calendar.cards.cancelSuccess', 'Event cancelled'), 'success')\n refetch()\n } catch (err) {\n // An optimistic-lock 409 is surfaced as the persistent conflict bar by\n // useGuardedMutation (surfaceRecordConflict) \u2014 don't re-flash.\n if (extractOptimisticLockConflict(err)) return\n flash(t('customers.calendar.cards.cancelError', 'Failed to cancel event'), 'error')\n }\n },\n [refetch, retryLastMutation, runMutation, t],\n )\n\n const focusSearch = React.useCallback(() => {\n const node = document.querySelector('[data-calendar-search]')\n if (node instanceof HTMLInputElement) node.focus()\n else if (node instanceof HTMLElement) node.querySelector('input')?.focus()\n }, [])\n\n const editorOpen = editor.open\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented) return\n if (event.metaKey || event.ctrlKey || event.altKey) return\n const target = event.target\n if (target instanceof HTMLElement) {\n const tag = target.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) return\n }\n if (editorOpen) return\n switch (event.key) {\n case 't':\n case 'T':\n event.preventDefault()\n setAnchor(new Date())\n setPreset(null)\n break\n case 'd':\n case 'D':\n event.preventDefault()\n setView('day')\n setPreset(null)\n break\n case 'w':\n case 'W':\n event.preventDefault()\n setView('week')\n setPreset(null)\n break\n case 'm':\n case 'M':\n event.preventDefault()\n setView('month')\n setPreset(null)\n break\n case 'a':\n case 'A':\n event.preventDefault()\n setView('agenda')\n setPreset(null)\n break\n case 'n':\n case 'N':\n event.preventDefault()\n openCreateEditor()\n break\n case '/':\n event.preventDefault()\n focusSearch()\n break\n case '?':\n event.preventDefault()\n setShortcutsOpen((open) => !open)\n break\n case 'Escape':\n setHighlightItemId(null)\n break\n default:\n break\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [editorOpen, focusSearch, openCreateEditor])\n\n const showInitialLoading = (isLoading && !hasLoadedOnce) || !preferencesHydrated\n\n let viewArea: React.ReactNode\n if (error) {\n viewArea = (\n <ErrorMessage\n label={t('customers.calendar.errors.loadFailed', 'Failed to load calendar events.')}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={refetch}>\n {t('customers.calendar.errors.retry', 'Retry')}\n </Button>\n }\n />\n )\n } else if (showInitialLoading) {\n viewArea = <LoadingMessage label={t('customers.calendar.loading', 'Loading calendar\u2026')} />\n } else if (view === 'month') {\n viewArea = (\n <MonthGrid anchor={anchor} items={viewItems} onItemClick={openEditEditor} onDayOpen={handleDayOpen} />\n )\n } else if (view === 'agenda') {\n viewArea = (\n <AgendaList\n anchor={anchor}\n horizonDays={agendaHorizonDays}\n items={viewItems}\n typeLabels={typeLabels}\n onItemClick={openEditEditor}\n />\n )\n } else {\n viewArea = (\n <TimeGrid\n days={view === 'day' ? 1 : 7}\n anchor={anchor}\n items={viewItems}\n conflictIds={conflictIds}\n showWeekends={preferences.showWeekends}\n showConflicts={preferences.conflictWarnings}\n aiSummaries={preferences.aiSummaries}\n canManage={canManage}\n highlightItemId={highlightItemId}\n onItemClick={openEditEditor}\n onJoin={handleJoin}\n onNavigate={handleTimeGridNavigate}\n onCreate={canManage ? openCreateEditor : undefined}\n onCreateRange={canManage ? handleCreateRange : undefined}\n />\n )\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col gap-4\">\n <CalendarHeader view={view} anchor={anchor} onNewEvent={canManage ? openCreateEditor : undefined} />\n <CalendarToolbar\n view={view}\n anchor={anchor}\n range={range}\n preset={preset}\n search={searchText}\n filters={filters}\n typeOptions={typeOptions}\n ownerOptions={ownerOptions}\n onToday={handleToday}\n onPresetChange={handlePresetChange}\n onAnchorChange={handleAnchorChange}\n onSearchChange={setSearchText}\n onFiltersChange={setFilters}\n onOpenSettings={() => setSettingsOpen(true)}\n />\n <UpcomingCards\n cards={upcomingCards}\n canManage={canManage}\n onJoin={handleJoin}\n onSeeConflict={handleSeeConflict}\n onOpen={openEditEditor}\n onEdit={openEditEditor}\n onCancel={handleCancelItem}\n />\n <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n <CalendarTabs\n tab={tab}\n counts={tabCounts}\n view={view}\n onTabChange={setTab}\n onViewChange={handleViewChange}\n />\n {truncated ? (\n <p className=\"text-xs text-muted-foreground\" role=\"status\">\n {t('customers.calendar.notice.truncated', 'Showing first {count} items for this range.', {\n count: MAX_WINDOW_ITEMS,\n })}\n </p>\n ) : null}\n <div className=\"flex min-h-[560px] flex-1 flex-col [&>*]:flex-1\">{viewArea}</div>\n </div>\n <div className=\"hidden md:block\">\n <CalendarFooter timezoneLabel={timezoneLabel} onOpenShortcuts={() => setShortcutsOpen(true)} />\n </div>\n <ShortcutsDialog open={shortcutsOpen} onOpenChange={setShortcutsOpen} />\n <CalendarSettingsModal\n open={settingsOpen}\n preferences={preferences}\n seedActivityTypes={seedActivityTypes}\n onOpenChange={setSettingsOpen}\n onSave={(next) => {\n setPreferences(next)\n flash(t('customers.calendar.settings.saved', 'Calendar settings saved'), 'success')\n }}\n />\n {editorMounted ? (\n <CalendarEventEditor\n open={editor.open}\n mode={editor.mode}\n item={editor.item}\n defaultDate={anchor}\n defaultRange={createRange}\n typeLabels={typeLabels}\n typeIcons={typeIcons}\n conflictScope={preferences.conflictScope}\n currentUserId={currentUserId}\n resourcesEnabled={resourcesEnabled}\n staffEnabled={staffEnabled}\n onOpenChange={(open) => setEditor((current) => ({ ...current, open }))}\n onSaved={refetch}\n />\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -124,7 +124,7 @@ const EventBlock = React.forwardRef(function EventBlock2({
124
124
  showTime ? /* @__PURE__ */ jsx("span", { className: cn("w-full truncate text-overline uppercase tracking-wide", tone.subClassName), children: timeRange }) : null,
125
125
  showMeta ? /* @__PURE__ */ jsxs("span", { className: "mt-auto flex w-full min-w-0 items-center gap-1.5", children: [
126
126
  visibleParticipants.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
127
- /* @__PURE__ */ jsx(AvatarStack, { size: "xs", max: MAX_VISIBLE_AVATARS, className: "shrink-0 gap-px [&>*:not(:first-child)]:-ml-1", children: visibleParticipants.map((participant) => /* @__PURE__ */ jsx(Avatar, { size: "xs", label: participantLabel(participant) }, participant.userId)) }),
127
+ /* @__PURE__ */ jsx(AvatarStack, { size: "xs", max: MAX_VISIBLE_AVATARS, className: "shrink-0 gap-px [&>*:not(:first-child)]:-ml-1", children: visibleParticipants.map((participant, index) => /* @__PURE__ */ jsx(Avatar, { size: "xs", label: participantLabel(participant) }, participant.userId ?? participant.email ?? index)) }),
128
128
  overflowCount > 0 ? /* @__PURE__ */ jsxs("span", { className: cn("shrink-0 text-xs", tone.subClassName), children: [
129
129
  "+",
130
130
  overflowCount
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/EventBlock.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Globe, MapPin } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { formatTimeRangeLabel } from '../../lib/calendar/format'\nimport type { CalendarItem, CalendarPlatform } from './types'\n\nconst SHOW_TIME_MIN_HEIGHT_PX = 44\nconst SHOW_META_MIN_HEIGHT_PX = 96\nconst WRAP_TITLE_MIN_HEIGHT_PX = 128\nconst MAX_VISIBLE_AVATARS = 3\n\nconst PLATFORM_LABELS: Record<CalendarPlatform, { key: string; fallback: string }> = {\n zoom: { key: 'customers.calendar.platform.zoom', fallback: 'Zoom' },\n meet: { key: 'customers.calendar.platform.meet', fallback: 'Meet' },\n slack: { key: 'customers.calendar.platform.slack', fallback: 'Slack' },\n teams: { key: 'customers.calendar.platform.teams', fallback: 'Teams' },\n}\n\nexport type EventTone = {\n surfaceClassName: string\n titleClassName: string\n subClassName: string\n style?: React.CSSProperties\n}\n\nfunction softTintStyle(color: string): React.CSSProperties {\n if (/^#[0-9a-fA-F]{6}$/.test(color)) return { backgroundColor: `${color}1A` }\n return { backgroundColor: `color-mix(in srgb, ${color} 10%, transparent)` }\n}\n\nexport function resolveEventTone(item: CalendarItem, nowMs: number): EventTone {\n if (item.status === 'canceled') {\n return {\n surfaceClassName: 'bg-muted/60',\n titleClassName: 'text-muted-foreground line-through',\n subClassName: 'text-muted-foreground/70',\n }\n }\n if (item.status === 'done' || item.end.getTime() < nowMs) {\n return {\n surfaceClassName: 'bg-muted',\n titleClassName: 'text-muted-foreground',\n subClassName: 'text-muted-foreground',\n }\n }\n if (item.color) {\n return {\n surfaceClassName: '',\n titleClassName: 'text-foreground',\n subClassName: 'text-muted-foreground',\n style: softTintStyle(item.color),\n }\n }\n return {\n surfaceClassName: 'bg-muted/60',\n titleClassName: 'text-foreground',\n subClassName: 'text-muted-foreground',\n }\n}\n\nexport function formatTimeRange(locale: string, start: Date, end: Date): string {\n return formatTimeRangeLabel(locale, start, end)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? '?'\n}\n\ntype LocationMetaProps = {\n item: CalendarItem\n subClassName: string\n accentColor: string | null\n}\n\nfunction LocationMeta({ item, subClassName, accentColor }: LocationMetaProps) {\n const t = useT()\n if (!item.location || !item.locationKind) return null\n const iconStyle = accentColor ? { color: accentColor } : undefined\n if (item.locationKind === 'platform' && item.platform) {\n const platform = PLATFORM_LABELS[item.platform]\n return (\n <span className={cn('truncate text-xs', subClassName)}>\n {t('customers.calendar.grid.onPlatform', 'on {platform}', { platform: t(platform.key, platform.fallback) })}\n </span>\n )\n }\n if (item.locationKind === 'url') {\n return (\n <>\n <Globe className={cn('size-4 shrink-0', !accentColor && subClassName)} style={iconStyle} aria-hidden />\n <span className=\"truncate text-xs text-foreground\">{item.location}</span>\n </>\n )\n }\n return (\n <>\n <MapPin className={cn('size-4 shrink-0', !accentColor && subClassName)} style={iconStyle} aria-hidden />\n <span className=\"truncate text-xs text-foreground\">\n {t('customers.calendar.grid.venue', 'Venue: {name}', { name: item.location })}\n </span>\n </>\n )\n}\n\nexport type EventBlockProps = {\n item: CalendarItem\n top: number\n height: number\n insetInlineStart: string\n width: string\n conflicted: boolean\n highlighted: boolean\n selected?: boolean\n nowMs: number\n} & Omit<React.ComponentProps<typeof Button>, 'style' | 'children'>\n\nexport const EventBlock = React.forwardRef<HTMLButtonElement, EventBlockProps>(function EventBlock(\n {\n item,\n top,\n height,\n insetInlineStart,\n width,\n conflicted,\n highlighted,\n selected,\n nowMs,\n className,\n ...buttonProps\n },\n ref,\n) {\n const t = useT()\n const locale = useLocale()\n const tone = resolveEventTone(item, nowMs)\n const title = item.title || t('customers.calendar.grid.untitled', 'Untitled')\n const timeRange = formatTimeRange(locale, item.start, item.end)\n const showTime = height >= SHOW_TIME_MIN_HEIGHT_PX\n const wrapTitle = height >= WRAP_TITLE_MIN_HEIGHT_PX\n const hasMetaContent = item.participants.length > 0 || (item.location !== null && item.locationKind !== null)\n const showMeta = height >= SHOW_META_MIN_HEIGHT_PX && hasMetaContent\n const visibleParticipants = item.participants.slice(0, MAX_VISIBLE_AVATARS)\n const overflowCount = item.participants.length - visibleParticipants.length\n\n return (\n <Button\n ref={ref}\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}, ${timeRange}`}\n className={cn(\n 'pointer-events-auto absolute h-auto flex-col items-start justify-start gap-1 whitespace-normal overflow-hidden rounded-md px-1.5 text-start outline-none hover:bg-muted/70 sm:px-3',\n height >= SHOW_TIME_MIN_HEIGHT_PX ? 'py-1 sm:py-2' : 'py-0.5 sm:py-1',\n tone.surfaceClassName,\n conflicted && 'ring-1 ring-status-warning-icon',\n selected && 'shadow-md ring-2 ring-foreground',\n highlighted && 'motion-safe:animate-pulse',\n conflicted || highlighted || selected ? 'z-30' : 'z-10',\n 'focus-visible:z-30 focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n style={{ top, height, insetInlineStart, width, ...tone.style }}\n {...buttonProps}\n >\n <span className={cn('w-full text-xs font-medium leading-4', wrapTitle ? 'line-clamp-2' : 'truncate', tone.titleClassName)}>\n {title}\n </span>\n {showTime ? (\n <span className={cn('w-full truncate text-overline uppercase tracking-wide', tone.subClassName)}>\n {timeRange}\n </span>\n ) : null}\n {showMeta ? (\n <span className=\"mt-auto flex w-full min-w-0 items-center gap-1.5\">\n {visibleParticipants.length > 0 ? (\n <>\n <AvatarStack size=\"xs\" max={MAX_VISIBLE_AVATARS} className=\"shrink-0 gap-px [&>*:not(:first-child)]:-ml-1\">\n {visibleParticipants.map((participant) => (\n <Avatar key={participant.userId} size=\"xs\" label={participantLabel(participant)} />\n ))}\n </AvatarStack>\n {overflowCount > 0 ? (\n <span className={cn('shrink-0 text-xs', tone.subClassName)}>+{overflowCount}</span>\n ) : null}\n </>\n ) : null}\n <span className=\"ms-auto flex min-w-0 items-center gap-1.5\">\n <LocationMeta item={item} subClassName={tone.subClassName} accentColor={item.color} />\n </span>\n </span>\n ) : null}\n </Button>\n )\n})\n\nEventBlock.displayName = 'EventBlock'\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Globe, MapPin } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { formatTimeRangeLabel } from '../../lib/calendar/format'\nimport type { CalendarItem, CalendarPlatform } from './types'\n\nconst SHOW_TIME_MIN_HEIGHT_PX = 44\nconst SHOW_META_MIN_HEIGHT_PX = 96\nconst WRAP_TITLE_MIN_HEIGHT_PX = 128\nconst MAX_VISIBLE_AVATARS = 3\n\nconst PLATFORM_LABELS: Record<CalendarPlatform, { key: string; fallback: string }> = {\n zoom: { key: 'customers.calendar.platform.zoom', fallback: 'Zoom' },\n meet: { key: 'customers.calendar.platform.meet', fallback: 'Meet' },\n slack: { key: 'customers.calendar.platform.slack', fallback: 'Slack' },\n teams: { key: 'customers.calendar.platform.teams', fallback: 'Teams' },\n}\n\nexport type EventTone = {\n surfaceClassName: string\n titleClassName: string\n subClassName: string\n style?: React.CSSProperties\n}\n\nfunction softTintStyle(color: string): React.CSSProperties {\n if (/^#[0-9a-fA-F]{6}$/.test(color)) return { backgroundColor: `${color}1A` }\n return { backgroundColor: `color-mix(in srgb, ${color} 10%, transparent)` }\n}\n\nexport function resolveEventTone(item: CalendarItem, nowMs: number): EventTone {\n if (item.status === 'canceled') {\n return {\n surfaceClassName: 'bg-muted/60',\n titleClassName: 'text-muted-foreground line-through',\n subClassName: 'text-muted-foreground/70',\n }\n }\n if (item.status === 'done' || item.end.getTime() < nowMs) {\n return {\n surfaceClassName: 'bg-muted',\n titleClassName: 'text-muted-foreground',\n subClassName: 'text-muted-foreground',\n }\n }\n if (item.color) {\n return {\n surfaceClassName: '',\n titleClassName: 'text-foreground',\n subClassName: 'text-muted-foreground',\n style: softTintStyle(item.color),\n }\n }\n return {\n surfaceClassName: 'bg-muted/60',\n titleClassName: 'text-foreground',\n subClassName: 'text-muted-foreground',\n }\n}\n\nexport function formatTimeRange(locale: string, start: Date, end: Date): string {\n return formatTimeRangeLabel(locale, start, end)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? '?'\n}\n\ntype LocationMetaProps = {\n item: CalendarItem\n subClassName: string\n accentColor: string | null\n}\n\nfunction LocationMeta({ item, subClassName, accentColor }: LocationMetaProps) {\n const t = useT()\n if (!item.location || !item.locationKind) return null\n const iconStyle = accentColor ? { color: accentColor } : undefined\n if (item.locationKind === 'platform' && item.platform) {\n const platform = PLATFORM_LABELS[item.platform]\n return (\n <span className={cn('truncate text-xs', subClassName)}>\n {t('customers.calendar.grid.onPlatform', 'on {platform}', { platform: t(platform.key, platform.fallback) })}\n </span>\n )\n }\n if (item.locationKind === 'url') {\n return (\n <>\n <Globe className={cn('size-4 shrink-0', !accentColor && subClassName)} style={iconStyle} aria-hidden />\n <span className=\"truncate text-xs text-foreground\">{item.location}</span>\n </>\n )\n }\n return (\n <>\n <MapPin className={cn('size-4 shrink-0', !accentColor && subClassName)} style={iconStyle} aria-hidden />\n <span className=\"truncate text-xs text-foreground\">\n {t('customers.calendar.grid.venue', 'Venue: {name}', { name: item.location })}\n </span>\n </>\n )\n}\n\nexport type EventBlockProps = {\n item: CalendarItem\n top: number\n height: number\n insetInlineStart: string\n width: string\n conflicted: boolean\n highlighted: boolean\n selected?: boolean\n nowMs: number\n} & Omit<React.ComponentProps<typeof Button>, 'style' | 'children'>\n\nexport const EventBlock = React.forwardRef<HTMLButtonElement, EventBlockProps>(function EventBlock(\n {\n item,\n top,\n height,\n insetInlineStart,\n width,\n conflicted,\n highlighted,\n selected,\n nowMs,\n className,\n ...buttonProps\n },\n ref,\n) {\n const t = useT()\n const locale = useLocale()\n const tone = resolveEventTone(item, nowMs)\n const title = item.title || t('customers.calendar.grid.untitled', 'Untitled')\n const timeRange = formatTimeRange(locale, item.start, item.end)\n const showTime = height >= SHOW_TIME_MIN_HEIGHT_PX\n const wrapTitle = height >= WRAP_TITLE_MIN_HEIGHT_PX\n const hasMetaContent = item.participants.length > 0 || (item.location !== null && item.locationKind !== null)\n const showMeta = height >= SHOW_META_MIN_HEIGHT_PX && hasMetaContent\n const visibleParticipants = item.participants.slice(0, MAX_VISIBLE_AVATARS)\n const overflowCount = item.participants.length - visibleParticipants.length\n\n return (\n <Button\n ref={ref}\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}, ${timeRange}`}\n className={cn(\n 'pointer-events-auto absolute h-auto flex-col items-start justify-start gap-1 whitespace-normal overflow-hidden rounded-md px-1.5 text-start outline-none hover:bg-muted/70 sm:px-3',\n height >= SHOW_TIME_MIN_HEIGHT_PX ? 'py-1 sm:py-2' : 'py-0.5 sm:py-1',\n tone.surfaceClassName,\n conflicted && 'ring-1 ring-status-warning-icon',\n selected && 'shadow-md ring-2 ring-foreground',\n highlighted && 'motion-safe:animate-pulse',\n conflicted || highlighted || selected ? 'z-30' : 'z-10',\n 'focus-visible:z-30 focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n style={{ top, height, insetInlineStart, width, ...tone.style }}\n {...buttonProps}\n >\n <span className={cn('w-full text-xs font-medium leading-4', wrapTitle ? 'line-clamp-2' : 'truncate', tone.titleClassName)}>\n {title}\n </span>\n {showTime ? (\n <span className={cn('w-full truncate text-overline uppercase tracking-wide', tone.subClassName)}>\n {timeRange}\n </span>\n ) : null}\n {showMeta ? (\n <span className=\"mt-auto flex w-full min-w-0 items-center gap-1.5\">\n {visibleParticipants.length > 0 ? (\n <>\n <AvatarStack size=\"xs\" max={MAX_VISIBLE_AVATARS} className=\"shrink-0 gap-px [&>*:not(:first-child)]:-ml-1\">\n {visibleParticipants.map((participant, index) => (\n <Avatar key={participant.userId ?? participant.email ?? index} size=\"xs\" label={participantLabel(participant)} />\n ))}\n </AvatarStack>\n {overflowCount > 0 ? (\n <span className={cn('shrink-0 text-xs', tone.subClassName)}>+{overflowCount}</span>\n ) : null}\n </>\n ) : null}\n <span className=\"ms-auto flex min-w-0 items-center gap-1.5\">\n <LocationMeta item={item} subClassName={tone.subClassName} accentColor={item.color} />\n </span>\n </span>\n ) : null}\n </Button>\n )\n})\n\nEventBlock.displayName = 'EventBlock'\n"],
5
+ "mappings": "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",
6
6
  "names": ["EventBlock"]
7
7
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { z } from "zod";
4
5
  import { cn } from "@open-mercato/shared/lib/utils";
5
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
7
  import { Avatar } from "@open-mercato/ui/primitives/avatar";
@@ -9,6 +10,11 @@ import { Input } from "@open-mercato/ui/primitives/input";
9
10
  import { composeAccessibleName } from "../../../lib/calendar/labels.js";
10
11
  import { searchPeopleOptions } from "./lookups.js";
11
12
  import { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge, useDropdownDismiss } from "./inputs.js";
13
+ const guestEmailSchema = z.string().email().max(320);
14
+ function normalizeEmail(email) {
15
+ const normalized = email?.trim().toLowerCase();
16
+ return normalized ? normalized : null;
17
+ }
12
18
  function PeopleField({
13
19
  mode,
14
20
  placeholder,
@@ -49,7 +55,14 @@ function PeopleField({
49
55
  }, [open, query, includeCustomers, includeStaff]);
50
56
  const selectedIds = new Set(value.map((participant) => participant.userId));
51
57
  const visibleOptions = options.filter((option) => !selectedIds.has(option.userId));
58
+ const typedEmail = normalizeEmail(query);
59
+ const selectedEmails = new Set(
60
+ value.map((participant) => normalizeEmail(participant.email)).filter((email) => email !== null)
61
+ );
62
+ const guestEmail = mode === "multi" && typedEmail !== null && guestEmailSchema.safeParse(typedEmail).success && !selectedEmails.has(typedEmail) && !options.some((option) => normalizeEmail(option.email) === typedEmail) ? typedEmail : null;
63
+ const guestLabel = guestEmail ? t("customers.calendar.editor.addGuest", "Invite {email} as a guest", { email: guestEmail }) : "";
52
64
  const customerBadge = /* @__PURE__ */ jsx(UppercaseBadge, { className: "bg-status-info-bg text-status-info-text", children: t("customers.calendar.editor.customerBadge", "Customer") });
65
+ const guestBadge = /* @__PURE__ */ jsx(UppercaseBadge, { className: "bg-muted text-muted-foreground", children: t("customers.calendar.editor.guestBadge", "Guest") });
53
66
  return /* @__PURE__ */ jsxs(
54
67
  "div",
55
68
  {
@@ -68,15 +81,15 @@ function PeopleField({
68
81
  CONTROL_BORDER
69
82
  ),
70
83
  children: [
71
- value.map((participant) => /* @__PURE__ */ jsx(
84
+ value.map((participant, index) => /* @__PURE__ */ jsx(
72
85
  PersonChip,
73
86
  {
74
87
  name: participant.name,
75
- badge: participant.isCustomer ? customerBadge : void 0,
76
- onRemove: () => onChange(value.filter((entry) => entry.userId !== participant.userId)),
88
+ badge: participant.isCustomer ? customerBadge : participant.userId ? void 0 : guestBadge,
89
+ onRemove: () => onChange(value.filter((_, entryIndex) => entryIndex !== index)),
77
90
  removeLabel: t("customers.calendar.editor.removePerson", "Remove {name}", { name: participant.name })
78
91
  },
79
- participant.userId
92
+ participant.userId ?? participant.email ?? index
80
93
  )),
81
94
  /* @__PURE__ */ jsx(
82
95
  Input,
@@ -98,7 +111,29 @@ function PeopleField({
98
111
  ),
99
112
  open ? /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-label": ariaLabel, className: DROPDOWN_PANEL_CLASS, children: [
100
113
  loading ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.searching", "Searching\u2026") }) : null,
101
- !loading && visibleOptions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.noResults", "No results") }) : null,
114
+ !loading && visibleOptions.length === 0 && !guestEmail ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.noResults", "No results") }) : null,
115
+ !loading && guestEmail ? /* @__PURE__ */ jsxs(
116
+ Button,
117
+ {
118
+ type: "button",
119
+ variant: "ghost",
120
+ role: "option",
121
+ "aria-selected": false,
122
+ "aria-label": guestLabel,
123
+ title: guestLabel,
124
+ onClick: () => {
125
+ const participant = { name: guestEmail, email: guestEmail, isCustomer: false };
126
+ onChange([...value, participant]);
127
+ setQuery("");
128
+ },
129
+ className: "h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground",
130
+ children: [
131
+ /* @__PURE__ */ jsx(Avatar, { size: "xs", label: guestEmail }),
132
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: guestLabel }),
133
+ guestBadge
134
+ ]
135
+ }
136
+ ) : null,
102
137
  !loading ? visibleOptions.map((option) => /* @__PURE__ */ jsxs(
103
138
  Button,
104
139
  {