Bitnoise / react-scheduler

MIT License
388 stars 107 forks source link
@bitnoise/react-scheduler

https://scheduler.bitnoise.pl/
Open sourced, typescript oriented, light-weight, and ultra fast React Component for creating gantt charts.

Youtube Tutorial   •   npm   •   Report an issue

NEWSLETTER

If you want to stay updated with Scheduler updates and news from the technical world, sign up for our newsletter. We don't spam (we send emails once a month), we don't run a sales newsletter, and we respect your time.

See for yourself: NEWSLETTER


Installation

# yarn
yarn add '@bitnoi.se/react-scheduler'
# npm
npm install '@bitnoi.se/react-scheduler'

Example usage

  1. import required styles for scheduler
import "@bitnoi.se/react-scheduler/dist/style.css";
  1. Import Scheduler component into your project
import { Scheduler, SchedulerData } from "@bitnoi.se/react-scheduler";
import dayjs from "dayjs";

default export function Component() {
  const [filterButtonState, setFilterButtonState] = useState(0);

  const [range, setRange] = useState({
    startDate: new Date(),
    endDate: new Date()
  });

  const handleRangeChange = useCallback((range) => {
    setRange(range);
  }, []);

  // Filtering events that are included in current date range
  // Example can be also found on video https://youtu.be/9oy4rTVEfBQ?t=118&si=52BGKSIYz6bTZ7fx
  // and in the react-scheduler repo App.tsx file https://github.com/Bitnoise/react-scheduler/blob/master/src/App.tsx
  const filteredMockedSchedulerData = mockedSchedulerData.map((person) => ({
        ...person,
        data: person.data.filter(
          (project) =>
            // we use "dayjs" for date calculations, but feel free to use library of your choice
            dayjs(project.startDate).isBetween(range.startDate, range.endDate) ||
            dayjs(project.endDate).isBetween(range.startDate, range.endDate) ||
            (dayjs(project.startDate).isBefore(range.startDate, "day") &&
              dayjs(project.endDate).isAfter(range.endDate, "day"))
        )
      }))

  return (
    <section>
      <Scheduler
        data={filteredMockedSchedulerData}
        isLoading={isLoading}
        onRangeChange={handleRangeChange}
        onTileClick={(clickedResource) => console.log(clickedResource)}
        onItemClick={(item) => console.log(item)}
        onFilterData={() => {
          // Some filtering logic...
          setFilterButtonState(1);
        }}
        onClearFilterData={() => {
          // Some clearing filters logic...
          setFilterButtonState(0)
        }}
        config={{
          zoom: 0,
          filterButtonState,
        }}
      />
    </section>
  );
}

const mockedSchedulerData: SchedulerData = [
  {
    id: "070ac5b5-8369-4cd2-8ba2-0a209130cc60",
    label: {
      icon: "https://picsum.photos/24",
      title: "Joe Doe",
      subtitle: "Frontend Developer"
    },
    data: [
      {
        id: "8b71a8a5-33dd-4fc8-9caa-b4a584ba3762",
        startDate: new Date("2023-04-13T15:31:24.272Z"),
        endDate: new Date("2023-08-28T10:28:22.649Z"),
        occupancy: 3600,
        title: "Project A",
        subtitle: "Subtitle A",
        description: "array indexing Salad West Account",
        bgColor: "rgb(254,165,177)"
      },
      {
        id: "22fbe237-6344-4c8e-affb-64a1750f33bd",
        startDate: new Date("2023-10-07T08:16:31.123Z"),
        endDate: new Date("2023-11-15T21:55:23.582Z"),
        occupancy: 2852,
        title: "Project B",
        subtitle: "Subtitle B",
        description: "Tuna Home pascal IP drive",
        bgColor: "rgb(254,165,177)"
      },
      {
        id: "3601c1cd-f4b5-46bc-8564-8c983919e3f5",
        startDate: new Date("2023-03-30T22:25:14.377Z"),
        endDate: new Date("2023-09-01T07:20:50.526Z"),
        occupancy: 1800,
        title: "Project C",
        subtitle: "Subtitle C",
        bgColor: "rgb(254,165,177)"
      },
      {
        id: "b088e4ac-9911-426f-aef3-843d75e714c2",
        startDate: new Date("2023-10-28T10:08:22.986Z"),
        endDate: new Date("2023-10-30T12:30:30.150Z"),
        occupancy: 11111,
        title: "Project D",
        subtitle: "Subtitle D",
        description: "Garden heavy an software Metal",
        bgColor: "rgb(254,165,177)"
      }
    ]
  }
];
  1. If some problems occur, please see our troubleshooting section below.

Scheduler API

Scheduler Component Props
Property Name Type Arguments Description
isLoading boolean - shows loading indicators on scheduler
onRangeChange function updated startDate and endDate runs whenever user reaches end of currently rendered canvas
onTileClick function clicked resource data detects resource click
onItemClick function clicked left column item data detects item click on left column
onFilterData function - callback firing when filter button was clicked
onClearFilterData function - callback firing when clear filters button was clicked (clearing button is visible only when filterButtonState is set to >0)
config Config - object with scheduler config properties
Scheduler Config Object

Property Name Type Default Description
zoom 0 or 1 or 2 0 0 - display grid divided into weeks 1 - display grid divided into days 2 - display grid divided into hours
filterButtonState number 0 < 0 - hides filter button, 0 - state for when filters were not set, > 0 - state for when some filters were set (allows to also handle onClearFilterData event)
maxRecordsPerPage number 50 number of items from SchedulerData visible per page
lang en, lt or pl en scheduler's language
includeTakenHoursOnWeekendsInDayView boolean false show weekends as taken when given resource is longer than a week
showTooltip boolean true show tooltip when hovering over tiles
translations LocaleType[] undefined option to add specific langs translations
showThemeToggle boolean false show toggle button to switch between light/dark mode
defaultTheme light or dark light scheduler's default theme

Translation object example

import enDayjsTranslations from "dayjs/locale/en";

const langs: LocaleType[] = [
  {
    id: "en",
    lang: {
      feelingEmpty: "I feel so empty...",
      free: "Free",
      loadNext: "Next",
      loadPrevious: "Previous",
      over: "over",
      taken: "Taken",
      topbar: {
        filters: "Filters",
        next: "next",
        prev: "prev",
        today: "Today",
        view: "View"
      },
      search: "search",
      week: "week"
    },
    translateCode: "en-EN",
    dayjsTranslations: enDayjsTranslations
  }
];

<Scheduler
  // ... //
  config={{
    lang: "en",
    translations: langs
  }}
/>;

Scheduler LocaleType Object

Property Name Type Description
id string key is needed for selecting lang
lang Translation object with translations
translateCode string code that is saved in localStorage
dayjsTranslations string ILocale undefined object with translation from dayjs

Scheduler Translation Object

Property Name Type
feelingEmpty string
free string
loadNext string
loadPrevious string
over string
taken string
search string
week string
topbar Topbar
Scheduler Topbar Object
Property Name Type
filters string
next string
prev string
today string
view string
Scheduler Data
array of chart rows with shape of Property Name Type Description
id string unique row id
label SchedulerRowLabel row's label, e.g person's name, surname, icon
data Array<ResourceItem> array of resources
Left Colum Item Data
data that is accessible as argument of onItemClick callback Property Name Type Description
id string unique row id
label SchedulerRowLabel row's label, e.g person's name, surname, icon
Resource Item
item that will be visible on the grid as tile and that will be accessible as argument of onTileClick event Property Name Type Description
id string unique resource id
title string resource title that will be displayed on resource tile
subtitle string (optional) resource subtitle that will be displayed on resource tile
description string (optional) resource description that will be displayed on resource tile
startDate Date date for calculating start position for resource
endDate Date date for calculating end position for resource
occupancy number number of seconds resource takes up for given row that will be visible on resource tooltip when hovered
bgColor string (optional) tile color

Troubleshooting

// remix.config.js
/** @type  {import('@remix-run/dev').AppConfig} */
module.exports = {
    // ...
    serverDependenciesToBundle: [..., "@bitnoi.se/react-scheduler"],
};
"use client"
import { Scheduler, SchedulerProps } from "@bitnoi.se/react-scheduler";

default export function SchedulerClient(props: SchedulerProps) {
    return <Scheduler {...props} />;
}
import dynamic from "next/dynamic";
const Scheduler = dynamic(() => import("@bitnoi.se/react-scheduler").then((mod) => mod.Scheduler), {
  ssr: false
});

Scheduler is position absolutely to take all available space. If you want to have fixed dimensions wrap Scheduler inside a div with position set to relative.

Example using styled components:

export const StyledSchedulerFrame = styled.div`
  position: relative;
  height: 40vh;
  width: 40vw;
`;

<StyledSchedulerFrame>
    <Scheduler  {...}/>
</StyledSchedulerFrame>

Known Issues

  1. No responsiveness
  2. Slower performance on Firefox when working with big set of data due to Firefox being slower working with canvas

How to contribute

Contact

If you have any questions or need further assistance, feel free to reach out to us at scheduler@bitnoi.se. We appreciate your contributions and thank you for helping us improve this project!

License

MIT Licensed. Copyright (c) Bitnoise 2023.