ra-calendar

react-admin ≥ 3.9.3

Display and manipulate events, drag and resize appointments, and browse a calendar in react-admin apps. This module integrates Full Calendar with react-admin, and supports:

  • month, week, day views
  • list view
  • drag and resize events
  • whole-day events
  • creating an event by clicking in the calendar
  • edition of event title, and metadata
  • events spanning on multiple days
  • recurring events
  • background events
  • theming
  • locales and timezones
  • resource time grid (e.g. rooms) (requires additional licence from Full Calendar)

Events Format

The calendar can display a list of Events. Events must be resources with at least a string id, a title, and a start date. Here is a typical event:

{
    id: '432646',
    title: 'Package delivery',
    start: '2020-10-20T08:00:00.000Z',
    end: '2020-10-20T09:30:00.000Z',
},

Events can have many more fields, e.g. for recurrent events, groups, colors, etc. Check the Event format on the Full Calendar documentation.

That means that in order to be able to use ra-calendar, your dataProvider must return event-like objects for at least one resource.

In addition, the calendar queries a list of events in a time interval. Your dataProvider must support getList() queries with an interval filter. By default, the interval filter looks like the following:

{
    // lower time boundary (gte stands for 'greater than or equal')
    start_gte: '2020-10-01T00:00:00.000Z',
    // upper time boundary (lte stands for 'less than or equal')
    start_lte: '2020-12-31T23:59:59.000Z'
}

The ra-calendar provides a function to transform the interval into a filter (explained below).

<Calendar>

A wrapper around full-calendar's <FullCalendar> component, using the react-admin Redux store as content provider, and linking to the edit and create views of the current resource. Must be used inside a <ListContext>.

Use this component as a child of <List>, as follows:

import { Calendar, getFilterValuesFromInterval } from '@react-admin/ra-calendar';
import { Admin, Resource, List, Edit, SimpleForm, TextInput, DateTimeInput } from 'react-admin';

const EventList: FC<ComponentProps<typeof List>> = props => (
    <List
        {...props}
        filterDefaultValues={getFilterValuesFromInterval()}
        perPage={1000}
        pagination={false}
    >
        <Calendar />
    </List>
);

const EventEdit: FC<ComponentProps<typeof Edit>> = props => (
    <Edit {...props}>
        <SimpleForm>
            <TextInput source="title" />
            <DateTimeInput source="start" />
            <DateTimeInput source="end" />
        </SimpleForm>
    </Edit>
);

const EventCreate: FC<ComponentProps<typeof Create>> = props => (
    <Create {...props}>
        <SimpleForm>
            <TextInput source="title" />
            <DateTimeInput source="start" />
            <DateTimeInput source="end" />
        </SimpleForm>
    </Create>
);

export const App: FC = () => (
    <Admin dataProvider={dataProvider}>
        <Resource
            name="events"
            list={EventList}
            edit={EventEdit}
            create={EventCreate}
        />
    </Admin>
);

The getFilterValuesFromInterval() function returns filter values based on the interval displayed on the screen (e.g. the current mont, the current week, etc). ra-calendar does its best to minimize queries to the dataProvider by requesting a 3 months interval by default (1 month before the current day, and 2 months after). You can change that behavior, and transform the filter object sent to the dataProvider.getList() method, by passing your own getFilterValueFromInterval prop:

import { DatesSetArg } from '@fullcalendar/react';
import { add, sub, set } from 'date-fns';

/**
 * By default, return an interval of 3 months around now (1 month before, 2 months after)
 * unless the user requires a larger interval.
 *
 * This minimizes queries while navigating.
 */
const customGetFilterValues = (
    dateInfo?: DatesSetArg,
    filterValues: any = {}
): any => {
    const now = set(new Date(), {
        hours: 0,
        minutes: 0,
        seconds: 0,
        milliseconds: 0,
    });
    const nowMinus1Month = sub(now, { months: 1 });
    const nowPlus2Months = add(now, { months: 2 });
    return !dateInfo ||
        (dateInfo.start > nowMinus1Month && dateInfo.end < nowPlus2Months)
        ? {
              ...filterValues,
              start_gte: nowMinus1Month.toISOString(),
              start_lte: nowPlus2Months.toISOString(),
          }
        : {
              ...filterValues,
              start_gte: dateInfo.startStr,
              start_lte: dateInfo.endStr,
          };
};

const EventList: FC<ComponentProps<typeof List>> = props => (
    <List
        {...props}
        filterDefaultValues={customGetFilterValues()}
        perPage={1000}
        pagination={false}
    >
        <Calendar getFilterValuesFromInterval={customGetFilterValues} />
    </List>
);
import { add, sub, set } from "date-fns";

/**
 * By default, return an interval of 3 months around now (1 month before, 2 months after)
 * unless the user requires a larger interval.
 *
 * This minimizes queries while navigating.
 */
const customGetFilterValues = (dateInfo, filterValues = {}) => {
    const now = set(new Date(), {
        hours: 0,
        minutes: 0,
        seconds: 0,
        milliseconds: 0,
    });
    const nowMinus1Month = sub(now, { months: 1 });
    const nowPlus2Months = add(now, { months: 2 });
    return !dateInfo || (dateInfo.start > nowMinus1Month && dateInfo.end < nowPlus2Months)
        ? {
              ...filterValues,
              start_gte: nowMinus1Month.toISOString(),
              start_lte: nowPlus2Months.toISOString(),
          }
        : {
              ...filterValues,
              start_gte: dateInfo.startStr,
              start_lte: dateInfo.endStr,
          };
};

const EventList = (props) => (
    <List {...props} filterDefaultValues={customGetFilterValues()} perPage={1000} pagination={false}>
        <Calendar getFilterValuesFromInterval={customGetFilterValues} />
    </List>
);

CHANGELOG

v0.0.1

2020-10-21

  • First release