# Plugin Calendar





Calendar view components for ObjectUI - includes both ObjectQL-integrated and standalone calendar components.

## Installation [#installation]

```bash
npm install @object-ui/plugin-calendar
```

<PluginLoader plugins="['calendar']">
  ## Overview [#overview]

  The `@object-ui/plugin-calendar` plugin provides two calendar components:

  1. **ObjectCalendar** (`object-calendar`): For ObjectQL data sources - displays database records as calendar events
  2. **CalendarView** (`calendar-view`): Standalone calendar component for displaying pre-loaded event data

  Both components support month, week, and day views with full event management capabilities.

  ## Drag-and-Drop Rescheduling [#drag-and-drop-rescheduling]

  ### Month view [#month-view]

  * **Move an event** — grab any cell of the event pill and drop it on another day. The grabbed-day → drop-day distance is the day delta applied to both `startDateField` and `endDateField`, so dragging a multi-day span from any of its days behaves intuitively.
  * **Resize an event** — grab the small right-edge handle on the last day of a multi-day pill and drop it on a different day to extend or shrink the end date. The start date is preserved; drops earlier than the start are ignored.

  ### Week / Day view (time grid) [#week--day-view-time-grid]

  Week and day views render a classic Google Calendar-style vertical time
  grid. All gestures use pointer events and snap to `slotMinutes` (default
  30\):

  * **Move an event** — drag the event body vertically to change its start
    time (and end, by the same delta). In week view, drag horizontally to
    also change the day.
  * **Resize start** — drag the top edge of an event to adjust only the
    start time. Refuses to cross the existing end.
  * **Resize end** — drag the bottom edge to adjust only the end time.
    Refuses to cross the existing start.
  * **Drag-to-create** — click-drag on an empty area of the time grid to
    open the quick-create dialog with start/end pre-filled to the dragged
    time range.

  Pass `slotMinutes={15}` to change the snap granularity, or
  `onTimeRangeSelect={(start, end) => …}` to override the drag-to-create
  default.

  When `ObjectCalendar` is bound to an object (i.e. it has `objectName` and a `dataSource`), the new dates are persisted automatically via `dataSource.update()` — local state is updated optimistically and rolled back on failure. To intercept (e.g. for a confirm dialog) pass an `onEventDrop` prop; supplying your own handler disables the default persistence.

  ```jsx
  <ObjectCalendar
    schema={{ type: 'object-calendar', objectName: 'campaign' }}
    // optional — omit to get default backend persistence
    onEventDrop={(record, newStart, newEnd) => {
      if (confirm(`Move ${record.name}?`)) save({ id: record.id, start_date: newStart, end_date: newEnd });
    }}
  />
  ```

  ## Click-to-Create [#click-to-create]

  Clicking the empty area of any day cell (month view) or click-dragging
  in the week/day time grid opens a quick-create dialog pre-filled with
  the selected date/range. Type a title and press <kbd>Enter</kbd> (or
  click **Create**) to persist a new record via
  `dataSource.create(objectName, payload)`. The new record is inserted
  optimistically into the calendar so it appears immediately.

  The payload includes the configured `titleField`, `startDateField`,
  optional `endDateField` (set to the clicked day), plus auto-defaults
  for any other required fields not provided by the user (first picklist
  option for `select`/`status`, `false` for booleans, `0` for numerics,
  or the field's declared `defaultValue`). This ensures the create
  succeeds against `NOT NULL` columns without forcing the user through
  the full form.

  To override (e.g. open your own multi-field create form), pass
  `onDateClick`:

  ```jsx
  <ObjectCalendar
    schema={{ type: 'object-calendar', objectName: 'campaign' }}
    onDateClick={(day) => navigate(`/campaign/new?start=${day.toISOString()}`)}
  />
  ```

  ## CalendarView Component [#calendarview-component]

  Full-featured standalone calendar with month, week, and day views for displaying events and scheduling.

  ### Interactive Examples [#interactive-examples]

  #### Month View [#month-view-1]

  <SchemaExample id="plugin-calendar/month-view-calendar" />

  #### Week View [#week-view]

  <SchemaExample id="plugin-calendar/week-view-calendar" />

  ### CalendarView Usage [#calendarview-usage]

  ```tsx
  import '@object-ui/plugin-calendar'
  import type { CalendarViewSchema } from '@object-ui/types'

  const schema: CalendarViewSchema = {
    type: 'calendar-view',
    view: 'month',
    data: [
      {
        id: 1,
        title: 'Team Meeting',
        start: '2024-01-15T10:00:00',
        end: '2024-01-15T11:00:00',
        color: '#3b82f6'
      }
    ]
  }
  ```

  ### CalendarView Schema API [#calendarview-schema-api]

  ```plaintext
  {
    type: 'calendar-view',
    view?: 'month' | 'week' | 'day',
    data?: Array<CalendarEventData>,
    titleField?: string,
    startDateField?: string,
    endDateField?: string,
    allDayField?: string,
    colorField?: string,
    currentDate?: string,
    allowCreate?: boolean,
    onEventClick?: (event: any) => void,
    onDateClick?: (date: Date) => void,
    onViewChange?: (view: string) => void,
    onNavigate?: (date: Date) => void,
    className?: string
  }
  ```

  ### Allowing event creation [#allowing-event-creation]

  `allowCreate: true` adds the header's **New event** button. Clicking it
  dispatches the standard create action — `{ type: 'create', payload: {} }` — on
  the same action channel every other `calendar-view` gesture uses, so the host
  decides what a "create" means (open a form, navigate, call an API).

  {/* doc-snippet: fragment — a SHAPE excerpt continuing the "CalendarView Usage" block above, which is where the event array is written inline: `events` is the READER's own data and is not defined in this block (measured: TS2552 x1) */}

  ```tsx
  const schema = {
    type: 'calendar-view',
    data: events,
    allowCreate: true,
  }
  ```

  Only the boolean `true` turns the button on. Omitting the key, `false`, and any
  non-boolean value all render the calendar without the button — the default.

  A React host can supply the affordance directly instead, with or without
  `allowCreate`, by passing its own `onAddClick` handler; an explicit handler
  replaces the action dispatch rather than running alongside it.

  ## ObjectCalendar Component [#objectcalendar-component]

  Calendar component designed for use with ObjectQL data sources.

  ### Features [#features]

  * **ObjectQL Integration**: Works seamlessly with object/value data providers
  * **Automatic Field Mapping**: Maps database fields to calendar events
  * **Multiple View Modes**: Month, week, and day calendar views
  * **Date Filtering**: Automatically filters records by date range
  * **Event Interaction**: Click handling for events and dates
  * **Color Coding**: Support for event color customization

  ### ObjectCalendar Usage [#objectcalendar-usage]

  #### Basic Usage with ObjectQL [#basic-usage-with-objectql]

  ```tsx
  import '@object-ui/plugin-calendar'
  import type { ObjectCalendarSchema } from '@object-ui/types'

  const schema: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'events',  // Your ObjectQL object
    calendar: {
      startDateField: 'startDate',
      endDateField: 'endDate',
      titleField: 'title',
      colorField: 'category'
    }
  }
  ```

  ### With Static Data [#with-static-data]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types'

  const schema: ObjectCalendarSchema = {
    type: 'object-calendar',
    staticData: [
      {
        id: 1,
        title: 'Team Meeting',
        startDate: '2024-01-15T10:00:00',
        endDate: '2024-01-15T11:00:00',
        category: 'meeting'
      },
      {
        id: 2,
        title: 'Project Deadline',
        startDate: '2024-01-20',
        category: 'deadline'
      }
    ],
    calendar: {
      startDateField: 'startDate',
      endDateField: 'endDate',
      titleField: 'title',
      colorField: 'category'
    }
  }
  ```

  ## Schema API [#schema-api]

  ```plaintext
  {
    type: 'object-calendar',
    objectName?: string,              // ObjectQL object name
    staticData?: Array<any>,          // Static data array
    data?: ViewData,                  // Advanced data configuration
    calendar?: CalendarConfig,        // Calendar-specific configuration
    onEventClick?: (record: any) => void,
    onDateClick?: (date: Date) => void,
    className?: string
  }
  ```

  ### CalendarConfig [#calendarconfig]

  ```plaintext
  {
    startDateField: string,     // Field containing event start date
    endDateField?: string,      // Field containing event end date
    titleField: string,         // Field to use as event title
    colorField?: string         // Field for color coding
  }
  ```

  `CalendarConfig` is `@objectstack/spec`'s `CalendarConfigSchema`, and that schema
  is **strict**: these four are the whole of it, and a fifth key is rejected rather
  than ignored. `ObjectCalendar` destructures exactly
  `{ startDateField, endDateField, titleField, colorField }`
  (`ObjectCalendar.tsx`), so the list above is also the whole of what the renderer
  reads.

  ## Configuration [#configuration]

  ### Field Mapping [#field-mapping]

  Map your database fields to calendar properties:

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const fieldMappedCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'tasks',
    calendar: {
      titleField: 'taskName',      // Database field for title
      startDateField: 'dueDate',   // Database field for start
      endDateField: 'completedAt', // Database field for end
      colorField: 'priority'       // Database field for color
    }
  };
  ```

  ### Data Providers [#data-providers]

  #### Object Provider (Database) [#object-provider-database]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const objectProviderCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'appointments',
    calendar: {
      startDateField: 'scheduledAt',
      titleField: 'subject'
    }
  };
  ```

  #### Value Provider (Static) [#value-provider-static]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const valueProviderCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    staticData: [
      { id: 1, title: 'Event 1', date: '2024-01-15' },
      { id: 2, title: 'Event 2', date: '2024-01-20' }
    ],
    calendar: {
      startDateField: 'date',
      titleField: 'title'
    }
  };
  ```

  #### API Provider — not implemented [#api-provider--not-implemented]

  `data.provider: 'api'` has no fetch implementation in `ObjectCalendar`. A schema
  that reaches this branch logs `API provider not yet implemented for
  ObjectCalendar`, sets the record set to empty and renders a calendar with no
  events; `endpoint` and `method` have no read point anywhere in the package.
  Without a `DataSource` it fails one step earlier, with `DataSource required for
  object/api providers`.

  Read from the database with the **Object Provider** above, or pass events you
  already hold with the **Value Provider**.

  ## Comparison: CalendarView vs ObjectCalendar [#comparison-calendarview-vs-objectcalendar]

  | Feature           | CalendarView          | ObjectCalendar            |
  | ----------------- | --------------------- | ------------------------- |
  | **Schema Type**   | `calendar-view`       | `object-calendar`         |
  | **Data Source**   | Static arrays         | ObjectQL (database)       |
  | **Use Case**      | Pre-loaded event data | Dynamic data from backend |
  | **Field Mapping** | Standard field names  | Configurable field names  |
  | **Data Loading**  | Manual via props      | Automatic via ObjectQL    |
  | **Best For**      | Static schedules      | Database-driven apps      |

  **When to use CalendarView**:

  * You have static or pre-loaded event data
  * You're not using ObjectQL
  * You need a simple, standalone calendar

  **When to use ObjectCalendar**:

  * You're using ObjectQL for data management
  * Events come from a database or API
  * You need automatic data fetching and filtering

  ## Event Handling [#event-handling]

  ### Event Click [#event-click]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const eventClickCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'events',
    calendar: {
      startDateField: 'start',
      titleField: 'title'
    },
    onEventClick: (record: any) => {
      console.log('Event clicked:', record);
      // Open event details
      // Navigate to event page
    }
  };
  ```

  ### Date Click [#date-click]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const dateClickCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'events',
    calendar: {
      startDateField: 'start',
      titleField: 'title'
    },
    onDateClick: (date: Date) => {
      console.log('Date clicked:', date);
      // Create new event on this date
    }
  };
  ```

  ## Examples [#examples]

  ### Appointment Scheduler [#appointment-scheduler]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const appointmentCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'appointments',
    calendar: {
      startDateField: 'appointmentDate',
      endDateField: 'appointmentEnd',
      titleField: 'patientName',
      colorField: 'appointmentType'
    },
    onEventClick: (appointment: any) => {
      // Show appointment details
    }
  };
  ```

  ### Event Management [#event-management]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const eventCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'events',
    calendar: {
      startDateField: 'eventStart',
      endDateField: 'eventEnd',
      titleField: 'eventTitle',
      colorField: 'eventCategory'
    },
    onEventClick: (event: any) => {
      // Navigate to event details
    },
    onDateClick: (date: Date) => {
      // Create new event
    }
  };
  ```

  ### Task Deadlines [#task-deadlines]

  ```tsx
  import type { ObjectCalendarSchema } from '@object-ui/types';

  const taskCalendar: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'tasks',
    calendar: {
      startDateField: 'dueDate',
      titleField: 'taskTitle',
      colorField: 'priority'
    },
    onEventClick: (task: any) => {
      // Open task details
    }
  };
  ```

  ## Direct Component Usage [#direct-component-usage]

  You can also import and use the components directly in React:

  {/* doc-snippet: fragment — the `ObjectCalendar` half cannot compile against the SHIPPED prop type: `ObjectCalendarComponentProps.schema` is declared `ObjectGridSchema | CalendarSchema`, and neither admits an `object-calendar` node — `ObjectGridSchema.type` is the literal `'object-grid'` and `CalendarSchema` is the FORM date picker (`type: 'calendar'`). The renderer registered for `object-calendar` passes exactly this shape (`index.tsx`), and `ObjectCalendar.tsx` reads `objectName` / `calendar` / `staticData` off it, so the runtime path is real and the declaration is the stale half — filed as objectui#7311 rather than papered over with a cast (measured: TS2322 x1) */}

  ```tsx
  import { CalendarView, ObjectCalendar } from '@object-ui/plugin-calendar';
  import type { DataSource } from '@object-ui/types';

  // CalendarView - Standalone calendar
  function MyCalendar() {
    const events = [
      {
        id: 1,
        title: 'Meeting',
        start: new Date('2024-01-15T10:00:00'),
        end: new Date('2024-01-15T11:00:00'),
        color: '#3b82f6'
      }
    ];

    return (
      <CalendarView
        events={events}
        view="month"
        onEventClick={(event) => console.log(event)}
      />
    );
  }

  // ObjectCalendar - ObjectQL-integrated
  function MyObjectCalendar({ dataSource }: { dataSource: DataSource }) {
    const schema = {
      objectName: 'events',
      calendar: {
        startDateField: 'startDate',
        titleField: 'title'
      }
    };

    return (
      <ObjectCalendar
        schema={schema}
        dataSource={dataSource}
        onEventClick={(record) => console.log(record)}
      />
    );
  }
  ```

  ## TypeScript Support [#typescript-support]

  ```plaintext
  import type { 
    CalendarViewSchema, 
    ObjectCalendarSchema,
    CalendarConfig 
  } from '@object-ui/types'
  import type { CalendarViewEvent } from '@object-ui/plugin-calendar'

  // CalendarView component events — the RUNTIME shape: `id: string | number`,
  // `start` / `end` are real `Date` objects. `@object-ui/types` exports a
  // separate `CalendarEvent`, the AUTHORING event (`id: string`, ISO strings,
  // `end` required); the two are not interchangeable. Renamed in objectui#5044,
  // which left `CalendarEvent` on this package as a `@deprecated` alias.
  const events: CalendarViewEvent[] = [
    {
      id: 1,
      title: 'Meeting',
      start: new Date('2024-01-15T10:00:00'),
      end: new Date('2024-01-15T11:00:00'),
      color: '#3b82f6'
    }
  ]

  const calendarViewSchema: CalendarViewSchema = {
    type: 'calendar-view',
    view: 'month',
    data: events
  }

  // ObjectCalendar types
  const calendarConfig: CalendarConfig = {
    startDateField: 'startDate',
    endDateField: 'endDate',
    titleField: 'title',
    colorField: 'category'
  }

  const objectCalendarSchema: ObjectCalendarSchema = {
    type: 'object-calendar',
    objectName: 'events',
    calendar: calendarConfig
  }
  ```

  ## Migration from @object-ui/plugin-calendar-view [#migration-from-object-uiplugin-calendar-view]

  The `@object-ui/plugin-calendar-view` package has been merged into this package. If you were using it:

  ### Before [#before]

  ```bash
  npm install @object-ui/plugin-calendar-view
  ```

  {/* doc-snippet: fragment — a migration guide's "Before" block, quoting the RETIRED `@object-ui/plugin-calendar-view` package on purpose: it was merged into `@object-ui/plugin-calendar` and no longer exists, so the specifier resolves nowhere and must not compile (measured: TS2882 x1, TS2307 x1). Correct documentation about code that is gone */}

  ```tsx
  import '@object-ui/plugin-calendar-view'
  import { CalendarView } from '@object-ui/plugin-calendar-view'
  ```

  ### After [#after]

  ```bash
  npm install @object-ui/plugin-calendar
  ```

  ```tsx
  import '@object-ui/plugin-calendar'
  import { CalendarView } from '@object-ui/plugin-calendar'
  ```

  All functionality remains the same - just update your imports and package dependencies.

  ## Related Documentation [#related-documentation]

  * [Plugin System Overview](/docs/guide/plugins)
  * [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-calendar)
</PluginLoader>
