import clsx from "clsx"; import { addMinutes, differenceInMinutes, format, getDay, parse, startOfWeek, } from "date-fns"; import React from "react"; import { Calendar, dateFnsLocalizer } from "react-big-calendar"; import { useMount } from "react-use"; import DateNavigationToolbar from "./date-navigation-toolbar"; import { DateTimeOption, DateTimePickerProps } from "./types"; import { formatDateWithoutTime, formatDateWithoutTz } from "./utils"; const localizer = dateFnsLocalizer({ format, parse, startOfWeek: (date: Date | number) => startOfWeek(date, { weekStartsOn: 1 }), getDay, locales: {}, }); const WeekCalendar: React.VoidFunctionComponent = ({ title, options, onNavigate, date, onChange, duration, onChangeDuration, }) => { const [scrollToTime, setScrollToTime] = React.useState(); useMount(() => { // Bit of a hack to force rbc to scroll to the right time when we close/open a modal setScrollToTime(addMinutes(date, -60)); }); return ( { if (option.type === "date") { return { title, start: new Date(option.date) }; } else { return { title, start: new Date(option.start), end: new Date(option.end), }; } })} onNavigate={onNavigate} date={date} className="h-[calc(100vh-220px)] max-h-[800px] min-h-[400px] w-full" defaultView="week" views={["week"]} selectable={true} localizer={localizer} onSelectEvent={(event) => { onChange( options.filter( (option) => !( option.type === "timeSlot" && option.start === formatDateWithoutTz(event.start) && event.end && option.end === formatDateWithoutTz(event.end) ), ), ); }} components={{ toolbar: (props) => { return ( { props.onNavigate("PREV"); }} onToday={() => { props.onNavigate("TODAY"); }} onNext={() => { props.onNavigate("NEXT"); }} /> ); }, eventWrapper: (props) => { return (
{format(props.event.start, "p")}
{props.event.title}
); }, week: { header: ({ date }: any) => { const dateString = formatDateWithoutTime(date); const selectedOption = options.find((option) => { return option.type === "date" && option.date === dateString; }); return ( { if (!selectedOption) { onChange([ ...options, { type: "date", date: formatDateWithoutTime(date), }, ]); } else { onChange( options.filter((option) => option !== selectedOption), ); } }} className={clsx( "inline-flex w-full justify-center hover:text-gray-700 hover:bg-slate-50 rounded-md items-center text-sm py-2", { "bg-green-50 text-green-600 hover:bg-opacity-75 hover:bg-green-50 hover:text-green-600": !!selectedOption, }, )} > {format(date, "E")} {format(date, "dd")} ); }, }, timeSlotWrapper: ({ children }) => { return
{children}
; }, }} step={15} onSelectSlot={({ start, end, action }) => { // on select slot const startDate = new Date(start); const endDate = new Date(end); const newEvent: DateTimeOption = { type: "timeSlot", start: formatDateWithoutTz(startDate), end: formatDateWithoutTz(endDate), }; if (action === "select") { const diff = differenceInMinutes(endDate, startDate); if (diff < 60 * 24) { onChangeDuration(diff); } } else { newEvent.end = formatDateWithoutTz(addMinutes(startDate, duration)); } const alreadyExists = options.some( (option) => option.type === "timeSlot" && option.start === newEvent.start && option.end === newEvent.end, ); if (!alreadyExists) { onChange([...options, newEvent]); } }} scrollToTime={scrollToTime} /> ); }; export default WeekCalendar;