Hooks

useDocyrusCalendar

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.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-calendar
Required Packages(3 packages)
pnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query

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

'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 <div>This data source has no date field.</div>;
  }

  return (
    <div className="space-y-4">
      {calendar.toolbar}

      <CalendarProvider {...calendar.calendarProviderProps}>
        <Calendar />
      </CalendarProvider>
    </div>
  );
}

With explicit field slugs and color mapping

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:

const calendar = useDocyrusCalendar({
  client,
  appSlug: 'base',
  dataSourceSlug: 'meeting',
  enableDateRangeFilter: true
});

return (
  <CalendarProvider
    {...calendar.calendarProviderProps}
    onVisibleRangeChange={calendar.setVisibleRange}>
    <Calendar />
  </CalendarProvider>
);

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.

const calendar = useDocyrusCalendar<MeetingRow>({
  client,
  appSlug: 'base',
  dataSourceSlug: 'meeting',
  getEvent: (item, ctx) => {
    if (item.status === 'cancelled') return null;

    const start = new Date(item.starts_at);
    const end = new Date(item.ends_at);

    return {
      id: ctx.index + 1,
      recordId: String(item.id),
      startDate: start.toISOString(),
      endDate: end.toISOString(),
      title: `${item.subject} (${item.location ?? 'TBD'})`,
      description: item.agenda ?? '',
      color: item.priority === 'high' ? 'red' : 'blue',
      user: {
        id: item.organizer_id,
        name: item.organizer_name,
        picturePath: item.organizer_avatar ?? null
      },
      item
    };
  }
});

Using a custom collection adapter

If you maintain a typed collection layer (auto-generated from @docyrus/tanstack-db-generator or hand-written) and want to reuse it for the calendar, pass it via collection. The hook will use the collection's list / create / update / delete methods instead of calling the REST endpoints directly:

const collection = useMeetingCollection();

const calendar = useDocyrusCalendar({
  client,
  appSlug: 'base',
  dataSourceSlug: 'meeting',
  collection
});

Customizing the calendar UI

calendarProviderProps wires only the minimum required to render events and persist changes to Docyrus (events, users, defaultView, filterItems, onEventCreate, onEventUpdate, onEventDelete, onEventDrop).

Every other CalendarProvider prop is a passthrough — spread calendarProviderProps first, then add the props you need:

<CalendarProvider
  {...calendar.calendarProviderProps}
  onVisibleRangeChange={calendar.setVisibleRange}
  // Replace the built-in event dialog with your own form
  onEventClick={(event) => openMeetingDialog(event.recordId)}
  // Open a side sheet when an empty time slot is clicked
  onSlotClick={(start, end) => openCreateSheet(start, end)}
  // Hide UI you don't want
  hideUserSelect
  hideSettings
  // Only show two views in the tab bar
  visibleViews={['week', 'month']}
  // Custom labels
  addButtonLabel="New meeting"
  eventCountLabel="meetings"
  searchPlaceholder="Search meetings…">
  <Calendar />
</CalendarProvider>

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.

PropTypeReplaces
onCreateEvent(date: Date) => voidThe 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) => voidThe built-in event-details dialog. Look up event.recordId to find the underlying Docyrus row.
onSlotClick(start: Date, end: Date) => voidClick handler for empty time slots in week/day view. Use it to start a "drag to create" flow.
onDaySelect(date: Date) => voidClick handler on day cells (fires regardless of whether a slot or event is hit).
onVisibleRangeChange(range, view) => voidNotifies on view switch / date navigation. Wire it to setVisibleRange to drive the date-range filter.

Header visibility toggles

PropDefaultEffect
hideHeaderAddButtonfalseHides the "Add Event" button in the header.
hideCellAddButtonfalseHides the "+" buttons that appear inside empty day cells.
hideAddButtonfalseDeprecated — hides both header and cell add buttons. Use the two flags above instead.
hideUserSelectfalseHides the user / filter dropdown in the header.
hideFilterfalseHides the color filter dropdown in the header.
hideSettingsfalseHides the settings gear (badge style, 24h format, agenda group-by).
hideEventCountfalseHides the event count badge that sits next to the date navigator.

Labels & placeholders

PropDefaultDescription
addButtonLabeltranslated "Add Event"Custom text for the header add button.
eventCountLabeltranslated "events"Custom unit for the count badge (e.g. "meetings", "leaves", "entries").
searchPlaceholdertranslated "Search…"Custom placeholder for the agenda view search input.

View configuration

PropTypeDescription
visibleViewsArray<TCalendarView>Which views to surface in the tab bar. Defaults to all five (day, week, month, year, agenda). Use ['week', 'month'] for a leaner board.
defaultViewTCalendarViewFirst 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:

PropTypeDescription
filterItemsArray<CalendarFilterItem>Replace the auto-derived list (e.g. show statuses, projects, or any other dimension). Each item is { id, label, image? }.
onFilterChange(id: string) => voidCalled when the user picks a filter item — wire it to your own data-source query filter.
renderFilterSelect(context) => ReactNodeRender prop to replace the filter dropdown entirely with custom UI.

Custom rendering

PropTypeDescription
sidebarContentReactNodeCustom content rendered alongside the calendar body (e.g. a list of upcoming events, mini month, or filter chips).
renderEventBadge(event, defaultBadge) => ReactNodeReplace 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.

SettingValuesDefault
Badge style'dot' | 'colored''colored'
Time format12h / 24h24h
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 <index>.

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:

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 actionWired toEffect
Create event in the UIonEventCreatePATCH /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 detailsonEventUpdatePATCH /items/:id with the patched start / end date fields.
Delete eventonEventDeleteDELETE /items/:id.
Drag-and-droponEventDropPATCH /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:

FieldUse it for
isLoadingtrue 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.
isFetchingtrue while a background refetch is in flight. Good for small spinners on the toolbar.
hasLoadedOnceFlips to true after the first successful items fetch and stays true. Use it to keep <Calendar> 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.

OptionTypeDefaultDescription
clientRestApiClient—Authenticated Docyrus API client.
appSlugstring—Target Docyrus app slug (e.g. 'base').
dataSourceSlugstring—Target data source slug.
collectionDocyrusCalendarCollection<TData>—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.
startDateFieldSlugstringautoSlug of the date / dateTime field used as event start. Auto-detected when omitted.
endDateFieldSlugstringautoSlug of the date / dateTime field used as event end. Auto-detected when omitted; falls back to defaultEventDurationMinutes when no end field is found.
titleFieldSlugstringautoSlug of the field used as event title. Auto-detected when omitted.
colorFieldSlugstringautoSlug of the field used to derive the event color. Auto-detected when omitted.
descriptionFieldSlugstringautoSlug of the field used as event description. Auto-detected when omitted.
userFieldSlugstringautoSlug of the user / userSelect field. Drives the calendar's user filter dropdown. Auto-detected when omitted.
colorMapRecord<string, TEventColor>—Map of raw color / enum-option values to TEventColor. Example: { 'Meeting': 'blue', 'Leave': 'red' }.
defaultEventDurationMinutesnumber60Fallback 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.
defaultViewTCalendarView'month'Default calendar view. One of 'day' | 'week' | 'month' | 'year' | 'agenda'.
enableDateRangeFilterbooleanfalseWhen true, the items query is narrowed to records overlapping the calendar's visible range. Pair with setVisibleRange on CalendarProvider.onVisibleRangeChange to drive it.
staleTimenumber30000TanStack Query staleTime for the items query (ms).
enableGroupMenubooleanfalseForwarded to the underlying data-grid toolbar. Off by default for calendar pages.
enableRowHeightMenubooleanfalseForwarded to the underlying data-grid toolbar.
enableDisplayMenubooleanfalseForwarded to the underlying data-grid toolbar.
onReload() => void—Forwarded to the underlying data-grid hook.

Return value

PropertyTypeDescription
calendarProviderPropsOmit<CalendarProviderProps, 'children'>Ready-to-spread props for <CalendarProvider>. 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.
eventsDocyrusCalendarEvent<TData>[]Transformed events ready for the calendar. Each event carries its original recordId and raw item.
usersIUser[]Unique users extracted from the user field. Drives the filter dropdown.
filterItemsCalendarFilterItem[]Generic filter items derived from users — already wired into calendarProviderProps.filterItems.
itemsTData[]All raw records returned by the items query.
startDateFieldDataSourceField | nullResolved start date field definition.
endDateFieldDataSourceField | nullResolved end date field definition (may be null when none exists).
titleFieldDataSourceField | nullResolved title field definition.
colorFieldDataSourceField | nullResolved color field definition.
descriptionFieldDataSourceField | nullResolved description field definition.
userFieldDataSourceField | nullResolved user field definition.
hasDateFieldbooleantrue 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<void>Persist a brand-new event to Docyrus. Use from custom "Add event" buttons.
updateRecord(recordId, data) => Promise<void>Patch an existing record by its UUID.
deleteRecord(recordId) => Promise<void>Delete a record by its UUID.
moveRecord(recordId, newStart, newEnd) => Promise<void>Move an event (drag-and-drop). Patches the start / end date fields.
visibleRange{ start: Date; end: Date } | nullCurrently active visible range — drives the date-range filter when enabled.
setVisibleRange(range | null) => voidSetter wired to CalendarProvider.onVisibleRangeChange.
requestedColumnsstringFinal comma-separated columns string sent to the items endpoint.
resolvedListParamsDocyrusDataGridListParamsFull query payload (filters, orderBy, limit, offset, columns) passed to the Docyrus items endpoint.
toolbarReactNodePrebuilt Docyrus data-view toolbar from useDocyrusDataGrid.
tableTable<TData>Underlying TanStack table used by the toolbar and saved-view editor.
viewsSavedDataGridView[]Saved views from useDocyrusDataGrid.
fieldsDataSourceField[]Full field list from useDocyrusDataGrid.
dataSourceDataSourceMetadata | nullResolved Docyrus data source metadata.
activeViewIdstring | nullCurrently active saved view id.
setActiveViewId(id) => voidSwitch the active saved view.
isLoadingbooleantrue until both metadata and items have finished their first fetch.
isFetchingbooleantrue while a refetch is in flight (initial or otherwise).
hasLoadedOncebooleantrue after the first successful items fetch — use to keep <Calendar> mounted across refetches.
errorError | nullFirst query error, if any.
reload() => voidRefetches the metadata and the items query.

Type Exports

TypeDescription
DocyrusCalendarEvent<TData>Calendar event extended with recordId and raw item.
DocyrusCalendarCollection<TData>Shape of an optional collection adapter (list / create / update / delete).
DocyrusCalendarEventBuilderContext<TData>Context passed to a custom getEvent builder (resolved fields + item + index).
UseDocyrusCalendarOptions<TData>Hook option shape (extends UseDocyrusDataGridOptions).
UseDocyrusCalendarResult<TData>Hook return shape.
IEventCalendar event shape (re-exported from @ui/components/calendar).
IUserCalendar 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.

On this page