i'm Trying to display the opening hours of a business in a react big calendar as backgroundEvents. The user should be able to add "special" opening hours, which should override the "normal" opening hours.
Heres an example of the data:
const openingHours = {
0: [
{ open: "04:00", close: "12:00" },
{ open: "13:00", close: "17:45" },
],
1: [{ open: "05:00", close: "17:45" }],
2: [{ open: "06:00", close: "17:45" }],
3: [{ open: "08:00", close: "17:45" }],
4: [
{ open: "06:30", close: "12:00" },
{ open: "13:30", close: "23:00" },
],
5: [{ open: "08:00", close: "17:45" }],
6: [],
};
const specialOpeningHours = [
{ open: "2024-01-05T05:00:00", close: "2024-01-05T12:00:00" },
{ open: "2024-01-05T13:00:00", close: "2024-01-05T23:59:00" },
];
i'm trying to update my Calendar when the onRangeChange hook is fired:
<DnDCalendar
localizer={localizer}
events={myEventsList}
startAccessor="start"
endAccessor="end"
backgroundEvents={backgroundEvents}
onRangeChange={handleRangeChange}
/>
this is my corresponding function:
const handleRangeChange = (range) => {
const dateRange = Array.isArray(range) ? range : [range.start, range.end];
console.log("Date Range:", dateRange);
const newBackgroundEvents = getBackgroundEvents(dateRange);
setBackgroundEvents(newBackgroundEvents);
};
const getBackgroundEvents = (range) => {
let events = [];
for (
let date = new Date(range[0]);
date <= range[1];
date.setDate(date.getDate() + 1)
) {
// Adjusting so that Monday = 0, Tuesday = 1, ..., Sunday = 6
const day = (date.getDay() + 6) % 7;
// Find if special hours apply to this date
const formattedDate = formatISO(date, { representation: "date" });
const specialHours = specialOpeningHours.filter((special) =>
special.open.startsWith(formattedDate)
);
// Determine hours to use (special or normal)
let hours = specialHours.length > 0 ? specialHours : openingHours[day];
// Convert hours to background events
hours.forEach((slot) => {
let start, end;
if (specialHours.length > 0) {
start = parseISO(slot.open);
end = parseISO(slot.close);
} else {
start = parseISO(`${formattedDate}T${slot.open}`);
end = parseISO(`${formattedDate}T${slot.close}`);
}
events.push({ start, end });
});
}
return events;
};
When running this function, the calendar does not update to the next or previous week but stays at the week i'm currently on.
i tried adding the "new" BackgroundEvents manually:
const handleRangeChange = (range) => {
const dateRange = Array.isArray(range) ? range : [range.start, range.end];
console.log("Date Range:", dateRange); // Add this line for debugging
const newBackgroundEvents = getBackgroundEvents(dateRange);
/*HERE*/
setBackgroundEvents([
{
start: parseISO("2024-01-07T13:05:00"),
end: parseISO("2024-01-07T14:00:00"),
},
]);
/*HERE*/
};
but this did not work. how can i update the backgroundEvents onRangeChange?
this is my complete code:
import "../../../styles/components/scheduler.scss";
import React, { useEffect, useState, useCallback, useRef } from "react";
import {
getISOWeek,
addYears,
subYears,
parseISO,
isSameDay,
formatISO,
} from "date-fns";
import {
Calendar,
dateFnsLocalizer,
Views,
CalendarProps,
} from "react-big-calendar";
import format from "date-fns/format";
import parse from "date-fns/parse";
import startOfWeek from "date-fns/startOfWeek";
import endOfWeek from "date-fns/endOfWeek";
import getDay from "date-fns/getDay";
import de from "date-fns/locale/de";
import withDragAndDrop from "react-big-calendar/lib/addons/dragAndDrop/withDragAndDrop";
const openingHours = {
0: [
{ open: "04:00", close: "12:00" },
{ open: "13:00", close: "17:45" },
],
1: [{ open: "05:00", close: "17:45" }],
2: [{ open: "06:00", close: "17:45" }],
3: [{ open: "08:00", close: "17:45" }],
4: [
{ open: "06:30", close: "12:00" },
{ open: "13:30", close: "23:00" },
],
5: [{ open: "08:00", close: "17:45" }],
6: [],
};
const specialOpeningHours = [
{ open: "2024-01-05T05:00:00", close: "2024-01-05T12:00:00" },
{ open: "2024-01-05T13:00:00", close: "2024-01-05T23:59:00" },
];
const myEventsList = [
{
title: "My Event",
start: parseISO("2024-01-07T13:05:00"),
end: parseISO("2024-01-07T14:00:00"),
},
{
title: "My Event 2",
start: parseISO("2024-01-07T13:35:00"),
end: parseISO("2024-01-07T14:30:00"),
},
{
title: "My Event 3",
start: parseISO("2024-01-12T13:35:00"),
end: parseISO("2024-01-12T14:30:00"),
},
];
function Scheduler() {
const [backgroundEvents, setBackgroundEvents] = useState([]);
const clickRef = useRef(null);
const getCurrentWeekRange = () => {
const today = new Date();
const start = startOfWeek(today, { weekStartsOn: 0 }); // Adjust weekStartsOn based on your locale
const end = endOfWeek(today, { weekStartsOn: 0 });
return [start, end];
};
useEffect(() => {
const currentWeekRange = getCurrentWeekRange();
handleRangeChange(currentWeekRange);
}, []);
useEffect(() => {
/* This is to prevent a memory leak, in the off chance that you
teardown your interface prior to the timed method being called.*/
return () => {
window.clearTimeout(clickRef?.current);
};
}, []);
const DnDCalendar = withDragAndDrop(Calendar);
const locales = {
de: de,
};
const messages = {
week: "Woche",
work_week: "Arbeitswoche",
day: "Tag",
month: "Monat",
previous: "zurück",
next: "vor",
today: "Heute",
agenda: "Liste",
showMore: (total) => `+${total} más`,
};
const localizer = dateFnsLocalizer({
format,
parse,
startOfWeek,
getDay,
locales,
});
function updateEvent(event) {
console.log(event);
console.log(event.start.toUTCString());
}
function newEvent(event) {
console.log(event);
}
const onSelectEvent = useCallback((calEvent) => {
console.log("onSelectEvent");
window.clearTimeout(clickRef?.current);
clickRef.current = window.setTimeout(() => {
console.log(calEvent);
}, 250);
}, []);
const onDoubleClickEvent = useCallback((calEvent) => {
console.log("onDoubleClick");
window.clearTimeout(clickRef?.current);
clickRef.current = window.setTimeout(() => {
console.log(calEvent);
}, 250);
}, []);
const handleRangeChange = (range) => {
const dateRange = Array.isArray(range) ? range : [range.start, range.end];
console.log("Date Range:", dateRange); // Add this line for debugging
const newBackgroundEvents = getBackgroundEvents(dateRange);
setBackgroundEvents([
{
start: parseISO("2024-01-07T13:05:00"),
end: parseISO("2024-01-07T14:00:00"),
},
]);
};
const getBackgroundEvents = (range) => {
let events = [];
for (
let date = new Date(range[0]);
date <= range[1];
date.setDate(date.getDate() + 1)
) {
// Adjusting so that Monday = 0, Tuesday = 1, ..., Sunday = 6
const day = (date.getDay() + 6) % 7;
// Find if special hours apply to this date
const formattedDate = formatISO(date, { representation: "date" });
const specialHours = specialOpeningHours.filter((special) =>
special.open.startsWith(formattedDate)
);
// Determine hours to use (special or normal)
let hours = specialHours.length > 0 ? specialHours : openingHours[day];
// Convert hours to background events
hours.forEach((slot) => {
let start, end;
if (specialHours.length > 0) {
start = parseISO(slot.open);
end = parseISO(slot.close);
} else {
start = parseISO(`${formattedDate}T${slot.open}`);
end = parseISO(`${formattedDate}T${slot.close}`);
}
events.push({ start, end });
});
}
return events;
};
return (
<div className="w-full h-full">
<DnDCalendar
defaultView="week"
selectable="true"
localizer={localizer}
culture="de"
events={myEventsList}
startAccessor="start"
endAccessor="end"
style={{ height: "100%" }}
messages={messages}
views={[Views.WEEK, Views.DAY]}
dayLayoutAlgorithm={"no-overlap"}
onEventDrop={updateEvent}
onEventResize={updateEvent}
onSelectEvent={onSelectEvent}
onSelectSlot={newEvent}
backgroundEvents={backgroundEvents}
onDoubleClick={onDoubleClickEvent}
onRangeChange={handleRangeChange}
/>
</div>
);
}
export default Scheduler;
Both the events
and backgroundEvents
are props. Updating the prop would update the calendar. I would also use controlled props for date
and view
. Do not forget that all dates, used by Big Calendar, should be true JS Date objects. This goes for your start/end for either type of 'event'.