# 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. |