Appearance
Calendar (operations)
Routes and navigation
- Route:
/calendar(same org-only area as tasks; in platform view the route redirects to the dashboard). - Access from the left sidebar (Calendar icon) and shortcut in the right bar (calendar button; hidden in platform view).
API
- List by visible range:
GET /operations/events?startDate=&endDate=(ISO). Bounds come from FullCalendar’sdatesSetcallback (range aligned with Month / Week / Agenda). - Create:
POST /operations/eventswith body validated byCreateEventSchemaon the backend (optionaltagIdsandattachmentIds). - Detail and edit: clicking an event opens a dialog using
GET /operations/events/:id; Edit sendsPATCH /operations/events/:id(UpdateEventSchema). In create/edit, name (title), type, and pills sit in a fixed header; the scrollable body holds description andEventTagsAttachments(EventScheduleFields/EventLocationField).EventFormFieldscomposes header +EventFormDescriptionSection. MapeventResponseToFormValues+ tests inevent-response-to-form-values.test.ts.
Pills (edit) and detail
- In create and edit, type includes values from
EVENT_TYPE_PRESETSplus a circular + button to define a custom type (free text sent astypeto the API). Dates, Location, Tags, and Attachments pills show or hide each block (event-dialog-sections.ts). When an event loads, sections that have data usually start visible (eventDialogSectionsFromEvent). - In read-only detail there are no pills: schedule, description (HTML via TipTap with
RichTextReadOnly), and location only if there is a value;EventDetailFilledExtrasadds tags and attachments when present (files with Open). Create/edit use the sameRichTextEditoras tasks. In create, when the description is empty,EventFormDescriptionSectionshows the shared dashed pill trigger with Pencil + placeholder (dialog-description-collapsed-trigger-classes.ts); after content exists, the section stays in editor form. tagIds/attachmentIdson create/update; cataloglistTags, storage (useFilesLibraryDialogStore+useFileOperations). In create and edit, the Etiquetas block uses the sameOperationTagsFieldas tasks/projects (chips, + opens the portal overlay to toggle tags, inline Crear etiqueta nueva viauseOperationTagCreate+OperationTagCreateFields, Listo (n)) — not a dropdown.- Helpers:
eventLinkedFilesFromEvent,resolveEventFormTags; testsevent-linked-files-from-event.test.ts,resolve-event-form-tags.test.ts,event-dialog-sections.test.ts.
Implementation
- UI: FullCalendar v6 (
@fullcalendar/react+ daygrid, timegrid, list, interaction). Theme incalendar-shell.css, aligned with Paneldaramex (calendar-theme.css): soft cell borders, month rows with height driven by content (month view only:height: autoonscrollgrid-sync-table; not applied to week so the time grid stays intact), and no cap on events per day (dayMaxEvents: false) in month. Month usesheight="auto"; week usesheight="100%"and a 24 h grid (slotMinTime00:00 /slotMaxTime24:00) withslotDuration/snapDuration15 minutes (minimum resize/move step and slot height),slotLabelInterval1 h for axis labels, andscrollTime07:00;.fc-timeGridWeek-viewrules to fill the harness. The dashboardOutletwrapper usesh-full min-h-0soheight: 100%resolves; week mode addsdaramex-fc--time-grid-weekso FullCalendar’s React root div participates in the flex column.CalendarGridcallsupdateSize()afterchangeView(doublerequestAnimationFrame) and uses aResizeObserverso FC remeasures when the flex layout settles; CSS addsmin-height: min(70vh, 56rem)on the week shell andfc-scrollgridso the slot grid stays visible if%heights still resolve to zero inside the dashboard scroll region. Agenda does not use FullCalendar’slistWeek: it is the layout styled like Paneldaramex (AgendaView+AgendaDayHeader+AgendaEventCard), calendar week Monday–Sunday (agenda-week-range.ts), list of days with API events grouped by day (group-events-by-agenda-days). Previous/next/today in agenda moves that weekly window; toolbar title is the day range for that week (formatAgendaToolbarTitle). In the header, month, week, and agenda share the same fixed title width as month so chevrons do not shift when changing period. - Data: TanStack Query in
useCalendarEventsQuery; DTOs map to FullCalendar entries inmapEventsToFullCalendar(including multi-day and all-day events: exclusive end per FullCalendar’s contract). In week view,allDaySlotis on: all-day and multi-day timed events (mapped toallDay) in the top band; the “all-day” axis label is hidden withallDayText="". Start time for multi-day events remains inextendedProps/CalendarEventContent. - Creation: dialog with shadcn (
Dialog,ToggleGroup,Checkbox, etc.) anduseCreateEventMutation, which invalidates['operations', 'events']. Default date/time: now → +1 h (buildCreateEventFormDefaults); if the user picked a day in month (local midnight), that day is kept with the current time; in week, a click with a time preserves that time. Drag-select on month or week (selectable,selectMirror,selectOverlap,selectMinDistance) opens the same dialog withbuildCreateEventFormDefaultsFromSelection(FC’s exclusiveend→ inclusive all-day last day or timed end instant). Operations module tags load withuseCalendarOperationTags(['operations', 'tags']). - Month and week views: events are draggable (
editable+eventDropon FullCalendar).resolveCalendarEventDropRangemaps the drop to API dates: in week time grid, timed events use FullCalendar’s new start/end (move time and preserve duration, Google Calendar–style). In month and for all-day rows (including multi-day timed in the all-day band), only the local calendar day delta is applied viashiftEventRangeByCalendarDays; clock times (or multi-day span) stay aligned with the original event. Resize is week-only (eventDurationEditable,eventResizableFromStart,eventResize): drag top/bottom to change start/end. Month does not allow resize (drag/move only).mapFullCalendarResizeToApiRangemaps FC’s range toPATCHdates for single-day timed events. All-day lane bars (true all-day and multi-day timed) usedurationEditable: falseinmap-events-to-fullcalendarso they cannot be resized in week view. Theme usesoverflow: visibleon.fc-timegrid-eventso week resize handles are not clipped. Updates useuseUpdateEventMutation(PATCH); on API error the drag or resize is reverted.
Tests
apps/panel/src/features/calendar/lib/map-events-to-fullcalendar.test.tscovers mapping timed and all-day events;agenda-week-range.test.tsandgroup-events-by-agenda-days.test.tscover the agenda view;shift-event-by-calendar-days.test.tscovers calendar-day shifting;resolve-calendar-event-drop-range.test.tscovers week vs month drag mapping;map-fullcalendar-resize-to-api-range.test.tscovers resize → API dates;build-create-event-form-defaults.test.tsincludesbuildCreateEventFormDefaultsFromSelection.