Overview

  • The React Calendar component can be configured to display a year view with months as columns.

  • Each calendar cell represents one day.

  • To make the calendar grid easier to read, each cell displays the day name (Mo, Tu, We…).

  • Calendar events can span multiple months (columns).

  • The React project includes a trial version of DayPilot Pro for JavaScript (see License below)

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Define Yearly Calendar Columns (One for Each Month)

React Yearly Calendar - Define Columns, One for Each Month

To create a yearly calendar in React, we will use the React resource calendar from DayPilot Pro.

We'll use the Calendar component in the Resources mode, which lets you define custom columns. In this case, the columns will not represent resources (people, rooms, vessels, machines, equipment, production lines…), but we will define columns with custom start dates, each representing one month of the current year.

Each column will display 31 days, regardless of the actual length of the month. We will shade the extra cells in shorter months in later steps.

Switch to "Resources" Mode

Set the viewType property to "Resources" to switch the calendar into resources mode:

<DayPilotCalendar
  viewType={"Resources"}
  // ...
/>

Set Cell Size to One Day

Set the scale property to "Day" to make each cell represent a single day:

<DayPilotCalendar
  scale={"Day"}
  // ...
/>

Define the First Day and Number of Days

Use startDate to define the first day of the calendar and days to set the number of days displayed in each column:

const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfYear());

// ...

<DayPilotCalendar
  startDate={startDate}
  days={31}
  // ...
/>

Generate Columns for Each Month

We'll create a columns state variable and populate it with months of the current year:

const [columns, setColumns] = useState([]);

useEffect(() => {
  const resources = [];
  // one column per month
  for (let i = 0; i < 12; i++) {
    const start = startDate.addMonths(i);
    const name = start.toString("MMMM");
    resources.push({ name, start });
  }
  setColumns(resources);
}, [startDate]);

Assing the Columns to the React Calendar Component

Include the columns prop in the DayPilotCalendar component:

<DayPilotCalendar
  columns={columns}
  // ...
/>

Show Day Numbers in the First Column

React Yearly Calendar - Show Day Numbers in the First Column

To display day numbers in the yearly calendar row headers, we'll use the onBeforeTimeHeaderRender event handler:

const onBeforeTimeHeaderRender = (args) => {
  const day = args.header.date.toString("d");
  args.header.html = day;
};

Attach this handler to the DayPilotCalendar React component tag:

<DayPilotCalendar
  onBeforeTimeHeaderRender={onBeforeTimeHeaderRender}
  // ...
/>

Gray Out Extra Days in Shorter Months

React Yearly Calendar - Gray Out Extra Days in Shorter Months

We want to make clear that the extra days at the end of columns that display shorter months (e.g., February, April, June…) are not active cells. We'll use the onBeforeCellRender event handler to set the background color to gray:

const onBeforeCellRender = (args) => {

  const belongsToCurrentMonth = args.cell.y + 1 === args.cell.start.getDay();
  if (!belongsToCurrentMonth) {
    args.cell.properties.backColor = "#e9e9e9";
  }

};

Attach this handler to the DayPilotCalendar component:

<DayPilotCalendar
  onBeforeCellRender={onBeforeCellRender}
  // ...
/>

Label Yearly Calendar Cells with Days of the Week

React Yearly Calendar - Label Cells with Days of the Week

To display the day names (e.g., Mo, Tu) in the React calendar cells, we'll modify the onBeforeCellRender handler and add an active area with the day of week as text:

const onBeforeCellRender = (args) => {
  const belongsToCurrentMonth = args.cell.y + 1 === args.cell.start.getDay();

  if (belongsToCurrentMonth) {
    args.cell.properties.areas = [
      {
        top: 0,
        left: 2,
        bottom: 0,
        width: 40,
        fontColor: "#dddddd",
        text: args.cell.start.toString("ddd"),
        verticalAlignment: "center",
      },
    ];
  } else {
    args.cell.properties.backColor = "#e9e9e9";
  }
};

Full Source Code

Below is the complete source code for the React yearly scheduling calendar UI component:

import React, { useEffect, useRef, useState } from 'react';
import { DayPilot, DayPilotCalendar } from "daypilot-pro-react";

const YearlyCalendar = () => {

  const [calendar, setCalendar] = useState(null);
  const [events, setEvents] = useState([]);
  const [columns, setColumns] = useState([]);
  const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfYear());

  const onTimeRangeSelected = async (args) => {
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
    const calendar = args.control;
    calendar.clearSelection();
    if (modal.canceled) { return; }
    calendar.events.add({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      text: modal.result
    });
  };

  const onBeforeTimeHeaderRender = args => {
    const day = args.header.date.toString("d");
    args.header.html = day;
  };

  const onBeforeCellRender = args => {
    const belongsToCurrentMonth = args.cell.y + 1 === args.cell.start.getDay();

    if (belongsToCurrentMonth) {
      args.cell.properties.areas = [
        { top: 0, left: 2, bottom: 0, width: 40, fontColor: "#dddddd", text: args.cell.start.toString("ddd"), verticalAlignment: "center" }
      ];
    }
    else {
      args.cell.backColor = "#e9e9e9";
    }
  };

  const onBeforeEventRender = args => {
    args.data.barHidden = true;
    args.data.borderRadius = "5px";
    args.data.borderColor = "darker";
    args.data.fontColor = "white";
    args.data.backColor = "#38761d66";

    args.data.html = "";
    args.data.areas = [
      {
        top: 3,
        left: 3,
        right: 3,
        height: 25,
        padding: 3,
        borderRadius: 5,
        backColor: "#38761d66",
        fontColor: "#ffffff",
        text: args.data.text
      },
      {
        top: 3,
        right: 3,
        height: 25,
        width: 25,
        borderRadius: "50%",
        padding: 5,
        backColor: "#38761d66",
        fontColor: "#ffffff",
        symbol: "/icons/daypilot.svg#x-2",
        onClick: async (args) => {
          calendar.events.remove(args.source);
        }
      }
    ];
  }

  const loadEvents = (args) => {
    const data = [
      {
        id: 1,
        text: "Event 1",
        start: DayPilot.Date.today().firstDayOfYear().addMonths(1).addDays(2),
        end: DayPilot.Date.today().firstDayOfYear().addMonths(1).addDays(5),
      },
      {
        id: 2,
        text: "Event 2",
        start: DayPilot.Date.today().firstDayOfYear().addMonths(2).addDays(2),
        end: DayPilot.Date.today().firstDayOfYear().addMonths(2).addDays(5),
      },
    ];
    setEvents(data);
  };

  useEffect(() => {
    const resources = [];
    // one column per month
    for (let i = 0; i < 12; i++) {
      const start = startDate.addMonths(i);
      const name = start.toString("MMMM");
      resources.push({name, start});
    }
    setColumns(resources);
  }, [startDate]);

  useEffect(() => {
    loadEvents();
  }, []);

  return (
    <div>
      <DayPilotCalendar
        scale={"Day"}
        viewType={"Resources"}
        startDate={startDate}
        days={31}
        cellHeight={40}
        columnMarginLeft={25}
        columnWidthMin={100}
        heightSpec={"Fixed"}
        height={600}
        events={events}
        columns={columns}
        onTimeRangeSelected={onTimeRangeSelected}
        onBeforeTimeHeaderRender={onBeforeTimeHeaderRender}
        onBeforeCellRender={onBeforeCellRender}
        onBeforeEventRender={onBeforeEventRender}
        controlRef={setCalendar}
      />
    </div>
  );
}
export default YearlyCalendar;

You can download the React project using the download link at the top of the article.