# useDocyrusCalendar URL: /docs/web/hooks/use-docyrus-calendar Wire a Docyrus data source to a fully configured calendar with auto-detected date / title / user fields, view-aware date-range fetching, color mapping, and drag-and-drop persistence. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-calendar ``` **Dependencies:** - [@docyrus/app-utils](https://www.npmjs.com/package/@docyrus/app-utils) - [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client) - [@tanstack/react-query](https://tanstack.com/query/latest) This hook is distributed as source. It expects an authenticated `RestApiClient` from `@docyrus/api-client` and a `QueryClientProvider` from `@tanstack/react-query` somewhere above your component tree. ## Overview `useDocyrusCalendar` is the calendar-first companion to [`useDocyrusDataGrid`](/docs/web/hooks/use-docyrus-data-grid). It maps a Docyrus data source to the `Calendar` component without forcing you to hand-build the event transformation: - loads the active Docyrus data source metadata - auto-detects the date / end-date / title / color / description / user fields, or uses the slugs you provide - keeps saved-view filtering, sorting, and search wiring from `useDocyrusDataGrid` - requests only the columns the calendar actually needs - transforms records into `IEvent` objects ready for `CalendarProvider` - wires create / update / delete / drag-and-drop callbacks to the Docyrus REST endpoints - optionally narrows the items query to records overlapping the calendar's visible range The hook returns a ready-to-spread `calendarProviderProps` object so the page code stays small. ## Usage ### Minimal page ```tsx 'use client'; import { useDocyrusAuth } from '@docyrus/signin'; import { Calendar, CalendarProvider } from '@docyrus/ui/components/calendar'; import { useDocyrusCalendar } from '@docyrus/ui/library/hooks/use-docyrus-calendar'; export function MeetingsCalendarPage() { const { client } = useDocyrusAuth(); if (!client) return null; const calendar = useDocyrusCalendar({ client, appSlug: 'base', dataSourceSlug: 'meeting', searchPlaceholder: 'Search meetings' }); if (!calendar.hasDateField) { return
This data source has no date field.
; } return (
{calendar.toolbar}
); } ``` ### With explicit field slugs and color mapping ```tsx const calendar = useDocyrusCalendar({ client, appSlug: 'base', dataSourceSlug: 'meeting', startDateFieldSlug: 'starts_at', endDateFieldSlug: 'ends_at', titleFieldSlug: 'subject', colorFieldSlug: 'meeting_type', descriptionFieldSlug: 'agenda', userFieldSlug: 'organizer', colorMap: { standup: 'blue', review: 'purple', leave: 'red', 'all-hands': 'orange' }, defaultView: 'week', defaultEventDurationMinutes: 30 }); ``` ### Visible-range filtering When events live in a large data source, fetching every record at once is wasteful. Set `enableDateRangeFilter: true` and the hook narrows the items query to the calendar's currently visible window using an overlap test: ```tsx const calendar = useDocyrusCalendar({ client, appSlug: 'base', dataSourceSlug: 'meeting', enableDateRangeFilter: true }); return ( ); ``` The hook will refetch every time the user navigates the calendar (next month, switch to week view, etc.). ### Custom event builder Use `getEvent` when the default mapping is not enough — for example to derive the color from multiple fields or to skip records that should not appear on the calendar. ```tsx const calendar = useDocyrusCalendar ``` ### Event interaction callbacks Each callback below **replaces the built-in dialog** for that interaction. Leave it out and the calendar uses its bundled add / details / delete dialogs. | Prop | Type | Replaces | |------|------|----------| | `onCreateEvent` | `(date: Date) => void` | The built-in add-event dialog. Use it to open your own form (e.g. a side sheet bound to the Docyrus form layout). | | `onEventClick` | `(event: IEvent) => void` | The built-in event-details dialog. Look up `event.recordId` to find the underlying Docyrus row. | | `onSlotClick` | `(start: Date, end: Date) => void` | Click handler for empty time slots in week/day view. Use it to start a "drag to create" flow. | | `onDaySelect` | `(date: Date) => void` | Click handler on day cells (fires regardless of whether a slot or event is hit). | | `onVisibleRangeChange` | `(range, view) => void` | Notifies on view switch / date navigation. Wire it to `setVisibleRange` to drive the date-range filter. | ### Header visibility toggles | Prop | Default | Effect | |------|---------|--------| | `hideHeaderAddButton` | `false` | Hides the "Add Event" button in the header. | | `hideCellAddButton` | `false` | Hides the "+" buttons that appear inside empty day cells. | | `hideAddButton` | `false` | **Deprecated** — hides both header and cell add buttons. Use the two flags above instead. | | `hideUserSelect` | `false` | Hides the user / filter dropdown in the header. | | `hideFilter` | `false` | Hides the color filter dropdown in the header. | | `hideSettings` | `false` | Hides the settings gear (badge style, 24h format, agenda group-by). | | `hideEventCount` | `false` | Hides the event count badge that sits next to the date navigator. | ### Labels & placeholders | Prop | Default | Description | |------|---------|-------------| | `addButtonLabel` | translated "Add Event" | Custom text for the header add button. | | `eventCountLabel` | translated "events" | Custom unit for the count badge (e.g. `"meetings"`, `"leaves"`, `"entries"`). | | `searchPlaceholder` | translated "Search…" | Custom placeholder for the agenda view search input. | ### View configuration | Prop | Type | Description | |------|------|-------------| | `visibleViews` | `Array` | Which views to surface in the tab bar. Defaults to all five (`day`, `week`, `month`, `year`, `agenda`). Use `['week', 'month']` for a leaner board. | | `defaultView` | `TCalendarView` | First view shown on mount. Pre-wired by the hook from `options.defaultView` (defaults to `'month'`). | | `badge` | `'dot' \| 'colored'` | Event badge style in the month view. Defaults to `'colored'`. The user can override via the settings gear unless `hideSettings` is set. | ### Filter customization The hook auto-derives `filterItems` from the user field. You can override the entire filter experience with these props: | Prop | Type | Description | |------|------|-------------| | `filterItems` | `Array` | Replace the auto-derived list (e.g. show statuses, projects, or any other dimension). Each item is `{ id, label, image? }`. | | `onFilterChange` | `(id: string) => void` | Called when the user picks a filter item — wire it to your own data-source query filter. | | `renderFilterSelect` | `(context) => ReactNode` | Render prop to replace the filter dropdown entirely with custom UI. | ### Custom rendering | Prop | Type | Description | |------|------|-------------| | `sidebarContent` | `ReactNode` | Custom content rendered alongside the calendar body (e.g. a list of upcoming events, mini month, or filter chips). | | `renderEventBadge` | `(event, defaultBadge) => ReactNode` | Replace the badge rendered for events in the month view. The second argument is the default node so you can wrap it. | ### Settings the user controls These live in the settings gear and persist to `localStorage` under the key `calendar-settings`. They are not part of `CalendarProvider`'s prop API — show or hide the gear with `hideSettings` to control whether the user can change them. | Setting | Values | Default | |---------|--------|---------| | Badge style | `'dot' \| 'colored'` | `'colored'` | | Time format | 12h / 24h | 24h | | Agenda group-by | `'date' \| 'color'` | `'date'` | ## How the query is built Internally the hook reuses the saved-view state from `useDocyrusDataGrid`, then creates its own items query with a stronger `columns` list. That means a user can save a view that hides the title or date field, but the hook will still request: - `id` - the resolved start date field - the resolved end date field (when present) - the resolved title field - the resolved color field (when present) - the resolved description field (when present) - the resolved user field (when present) So events keep rendering correctly while the rest of the data-view behavior still follows the active Docyrus view. ## Field resolution ### Start date field Resolution order: 1. `startDateFieldSlug` 2. first field with `type === 'field-date'` or `'field-dateTime'` If no start date field can be resolved, the hook returns `hasDateField: false` and renders no events. ### End date field Resolution order: 1. `endDateFieldSlug` 2. first matching slug from: `end_date`, `end_time`, `end_datetime`, `finish_date`, `due_date`, `deadline`, `ends_at`, `ended_at` (excluding the start field) 3. first date / dateTime field different from the start field When no end date field is available, the hook falls back to `defaultEventDurationMinutes` (60 minutes by default) to compute each event's end. ### Title field Resolution order: 1. `titleFieldSlug` 2. first matching slug from: `name`, `title`, `label`, `subject`, `display_name`, `displayName`, `commercial_title`, `short_name` If no title can be resolved, events fall back to `Event `. ### Color field Resolution order: 1. `colorFieldSlug` 2. field whose slug is `color` or `colour` ### Description field Resolution order: 1. `descriptionFieldSlug` 2. first matching slug from: `description`, `notes`, `content`, `details`, `body`, `summary` (excluding the title field) ### User field Resolution order: 1. `userFieldSlug` 2. first field with `type === 'field-userSelect'` or `'field-userMultiSelect'` 3. field whose slug is `record_owner` User values shape `users[]` (unique list used by the calendar's filter dropdown) and the `filterItems[]` array that powers the header filter. ## Color resolution Each record's color is normalized through this pipeline: 1. Read the raw value from `colorField` (string, enum option name, or object with a `name` / `label` / `title` / `value` string property). 2. Lower-case and trim the value. 3. Look up the value in `colorMap` (both original casing and lower-cased keys are tried). 4. If the value is already one of `'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange'`, use it as-is. 5. Otherwise default to `'blue'`. This means a `field-select` whose option names are `Standup`, `Review`, `Leave` etc. can be mapped to event colors with a small map: ```tsx colorMap: { standup: 'blue', review: 'purple', leave: 'red' } ``` ## Persistence (CRUD wiring) `calendarProviderProps` plugs the calendar's lifecycle events into the Docyrus REST endpoints (or your custom `collection` adapter when provided): | Calendar action | Wired to | Effect | |------|----------|--------| | Create event in the UI | `onEventCreate` | `PATCH /items/:id` with the new start / end on the matching record (events are created through the UI from existing records — use the explicit `createRecord` helper to create a brand-new row). | | Edit event details | `onEventUpdate` | `PATCH /items/:id` with the patched start / end date fields. | | Delete event | `onEventDelete` | `DELETE /items/:id`. | | Drag-and-drop | `onEventDrop` | `PATCH /items/:id` with new start / end. | If you need to create a new Docyrus record from the calendar (for example from an "Add event" toolbar button), call the exposed `createRecord(startDate, endDate, extraValues?)` helper. It posts to the items endpoint (or your `collection.create`) and reloads the calendar. ## Loading lifecycle The hook exposes three loading-related fields that map to different UX needs: | Field | Use it for | |------|-------| | `isLoading` | `true` while either the metadata or the items query is still running for the first time. Combine with `hasLoadedOnce` to render a full-screen skeleton only on first mount. | | `isFetching` | `true` while a background refetch is in flight. Good for small spinners on the toolbar. | | `hasLoadedOnce` | Flips to `true` after the first successful items fetch and stays true. Use it to keep `` mounted on subsequent refetches — passing `isLoading` directly would unmount the provider and reset its internal `selectedDate` / `view` state. | ## API Reference ### `useDocyrusCalendar(options)` The hook accepts every `useDocyrusDataGrid` option except `data`, `enableItemsQuery`, `showSelectColumn`, `enableRowMarkers`, and `collection` (replaced by the `DocyrusCalendarCollection` shape). It also accepts the options below. | Option | Type | Default | Description | |--------|------|---------|-------------| | `client` | `RestApiClient` | — | Authenticated Docyrus API client. | | `appSlug` | `string` | — | Target Docyrus app slug (e.g. `'base'`). | | `dataSourceSlug` | `string` | — | Target data source slug. | | `collection` | `DocyrusCalendarCollection` | — | Optional collection adapter (e.g. typed `@docyrus/tanstack-db-generator` output). When provided, the hook calls `collection.list / create / update / delete` instead of hitting REST endpoints directly. | | `startDateFieldSlug` | `string` | auto | Slug of the date / dateTime field used as event start. Auto-detected when omitted. | | `endDateFieldSlug` | `string` | auto | Slug of the date / dateTime field used as event end. Auto-detected when omitted; falls back to `defaultEventDurationMinutes` when no end field is found. | | `titleFieldSlug` | `string` | auto | Slug of the field used as event title. Auto-detected when omitted. | | `colorFieldSlug` | `string` | auto | Slug of the field used to derive the event color. Auto-detected when omitted. | | `descriptionFieldSlug` | `string` | auto | Slug of the field used as event description. Auto-detected when omitted. | | `userFieldSlug` | `string` | auto | Slug of the user / userSelect field. Drives the calendar's user filter dropdown. Auto-detected when omitted. | | `colorMap` | `Record` | — | Map of raw color / enum-option values to `TEventColor`. Example: `{ 'Meeting': 'blue', 'Leave': 'red' }`. | | `defaultEventDurationMinutes` | `number` | `60` | Fallback event length used when the end date field is absent or empty. | | `getEvent` | `(item, context) => DocyrusCalendarEvent \| null` | — | Override event construction. Return `null` to skip a record entirely. | | `defaultView` | `TCalendarView` | `'month'` | Default calendar view. One of `'day' \| 'week' \| 'month' \| 'year' \| 'agenda'`. | | `enableDateRangeFilter` | `boolean` | `false` | When `true`, the items query is narrowed to records overlapping the calendar's visible range. Pair with `setVisibleRange` on `CalendarProvider.onVisibleRangeChange` to drive it. | | `staleTime` | `number` | `30000` | TanStack Query `staleTime` for the items query (ms). | | `enableGroupMenu` | `boolean` | `false` | Forwarded to the underlying data-grid toolbar. Off by default for calendar pages. | | `enableRowHeightMenu` | `boolean` | `false` | Forwarded to the underlying data-grid toolbar. | | `enableDisplayMenu` | `boolean` | `false` | Forwarded to the underlying data-grid toolbar. | | `onReload` | `() => void` | — | Forwarded to the underlying data-grid hook. | ### Return value | Property | Type | Description | |----------|------|-------------| | `calendarProviderProps` | `Omit` | Ready-to-spread props for ``. Wires only `events`, `users`, `defaultView`, `filterItems`, and the CRUD callbacks (`onEventCreate`, `onEventUpdate`, `onEventDelete`, `onEventDrop`). All other `CalendarProvider` props (`onEventClick`, `onSlotClick`, `onDaySelect`, `visibleViews`, hide-flags, custom labels, custom renderers…) are passthroughs you add alongside — see [Customizing the calendar UI](#customizing-the-calendar-ui). | | `events` | `DocyrusCalendarEvent[]` | Transformed events ready for the calendar. Each event carries its original `recordId` and raw `item`. | | `users` | `IUser[]` | Unique users extracted from the user field. Drives the filter dropdown. | | `filterItems` | `CalendarFilterItem[]` | Generic filter items derived from `users` — already wired into `calendarProviderProps.filterItems`. | | `items` | `TData[]` | All raw records returned by the items query. | | `startDateField` | `DataSourceField \| null` | Resolved start date field definition. | | `endDateField` | `DataSourceField \| null` | Resolved end date field definition (may be `null` when none exists). | | `titleField` | `DataSourceField \| null` | Resolved title field definition. | | `colorField` | `DataSourceField \| null` | Resolved color field definition. | | `descriptionField` | `DataSourceField \| null` | Resolved description field definition. | | `userField` | `DataSourceField \| null` | Resolved user field definition. | | `hasDateField` | `boolean` | `true` when a usable start date field was resolved. Use it to render a fallback when the data source is incompatible. | | `createRecord` | `(startDate, endDate, extraValues?) => Promise` | Persist a brand-new event to Docyrus. Use from custom "Add event" buttons. | | `updateRecord` | `(recordId, data) => Promise` | Patch an existing record by its UUID. | | `deleteRecord` | `(recordId) => Promise` | Delete a record by its UUID. | | `moveRecord` | `(recordId, newStart, newEnd) => Promise` | Move an event (drag-and-drop). Patches the start / end date fields. | | `visibleRange` | `{ start: Date; end: Date } \| null` | Currently active visible range — drives the date-range filter when enabled. | | `setVisibleRange` | `(range \| null) => void` | Setter wired to `CalendarProvider.onVisibleRangeChange`. | | `requestedColumns` | `string` | Final comma-separated columns string sent to the items endpoint. | | `resolvedListParams` | `DocyrusDataGridListParams` | Full query payload (filters, orderBy, limit, offset, columns) passed to the Docyrus items endpoint. | | `toolbar` | `ReactNode` | Prebuilt Docyrus data-view toolbar from `useDocyrusDataGrid`. | | `table` | `Table` | Underlying TanStack table used by the toolbar and saved-view editor. | | `views` | `SavedDataGridView[]` | Saved views from `useDocyrusDataGrid`. | | `fields` | `DataSourceField[]` | Full field list from `useDocyrusDataGrid`. | | `dataSource` | `DataSourceMetadata \| null` | Resolved Docyrus data source metadata. | | `activeViewId` | `string \| null` | Currently active saved view id. | | `setActiveViewId` | `(id) => void` | Switch the active saved view. | | `isLoading` | `boolean` | `true` until both metadata and items have finished their first fetch. | | `isFetching` | `boolean` | `true` while a refetch is in flight (initial or otherwise). | | `hasLoadedOnce` | `boolean` | `true` after the first successful items fetch — use to keep `` mounted across refetches. | | `error` | `Error \| null` | First query error, if any. | | `reload` | `() => void` | Refetches the metadata and the items query. | ## Type Exports | Type | Description | |------|-------------| | `DocyrusCalendarEvent` | Calendar event extended with `recordId` and raw `item`. | | `DocyrusCalendarCollection` | Shape of an optional collection adapter (`list / create / update / delete`). | | `DocyrusCalendarEventBuilderContext` | Context passed to a custom `getEvent` builder (resolved fields + item + index). | | `UseDocyrusCalendarOptions` | Hook option shape (extends `UseDocyrusDataGridOptions`). | | `UseDocyrusCalendarResult` | Hook return shape. | | `IEvent` | Calendar event shape (re-exported from `@ui/components/calendar`). | | `IUser` | Calendar user shape (re-exported from `@ui/components/calendar`). | | `TCalendarView` | `'day' \| 'week' \| 'month' \| 'year' \| 'agenda'`. | | `TEventColor` | `'blue' \| 'green' \| 'red' \| 'yellow' \| 'purple' \| 'orange'`. | | `CalendarFilterItem` | `{ id, label, image? }` filter dropdown item. |