2023-06-15 09:59:58 +00:00
|
|
|
import React, { useEffect, useMemo, useRef } from "react";
|
|
|
|
|
|
|
|
import { classNames } from "@calcom/lib";
|
2022-12-14 13:36:10 +00:00
|
|
|
|
|
|
|
import { useCalendarStore } from "../state/store";
|
|
|
|
import "../styles/styles.css";
|
2023-04-05 18:14:46 +00:00
|
|
|
import type { CalendarComponentProps } from "../types/state";
|
2023-06-15 09:59:58 +00:00
|
|
|
import { getDaysBetweenDates, getHoursToDisplay } from "../utils";
|
2022-12-14 13:36:10 +00:00
|
|
|
import { DateValues } from "./DateValues";
|
2023-06-15 09:59:58 +00:00
|
|
|
import { CurrentTime } from "./currentTime";
|
|
|
|
import { AvailableCellsForDay, EmptyCell } from "./event/Empty";
|
2022-12-14 13:36:10 +00:00
|
|
|
import { EventList } from "./event/EventList";
|
|
|
|
import { SchedulerColumns } from "./grid";
|
|
|
|
import { SchedulerHeading } from "./heading/SchedulerHeading";
|
|
|
|
import { HorizontalLines } from "./horizontalLines";
|
2023-06-15 09:59:58 +00:00
|
|
|
import { Spinner } from "./spinner/Spinner";
|
2022-12-14 13:36:10 +00:00
|
|
|
import { VeritcalLines } from "./verticalLines";
|
|
|
|
|
|
|
|
export function Calendar(props: CalendarComponentProps) {
|
|
|
|
const container = useRef<HTMLDivElement | null>(null);
|
|
|
|
const containerNav = useRef<HTMLDivElement | null>(null);
|
|
|
|
const containerOffset = useRef<HTMLDivElement | null>(null);
|
2023-06-09 09:38:18 +00:00
|
|
|
const schedulerGrid = useRef<HTMLOListElement | null>(null);
|
2022-12-14 13:36:10 +00:00
|
|
|
const initalState = useCalendarStore((state) => state.initState);
|
|
|
|
|
|
|
|
const startDate = useCalendarStore((state) => state.startDate);
|
|
|
|
const endDate = useCalendarStore((state) => state.endDate);
|
|
|
|
const startHour = useCalendarStore((state) => state.startHour || 0);
|
|
|
|
const endHour = useCalendarStore((state) => state.endHour || 23);
|
|
|
|
const usersCellsStopsPerHour = useCalendarStore((state) => state.gridCellsPerHour || 4);
|
2023-06-09 09:38:18 +00:00
|
|
|
const availableTimeslots = useCalendarStore((state) => state.availableTimeslots);
|
|
|
|
const hideHeader = useCalendarStore((state) => state.hideHeader);
|
2022-12-14 13:36:10 +00:00
|
|
|
|
|
|
|
const days = useMemo(() => getDaysBetweenDates(startDate, endDate), [startDate, endDate]);
|
|
|
|
const hours = useMemo(() => getHoursToDisplay(startHour || 0, endHour || 23), [startHour, endHour]);
|
|
|
|
const numberOfGridStopsPerDay = hours.length * usersCellsStopsPerHour;
|
2023-06-15 09:59:58 +00:00
|
|
|
const hourSize = 58;
|
2022-12-14 13:36:10 +00:00
|
|
|
|
|
|
|
// Initalise State on inital mount
|
|
|
|
useEffect(() => {
|
|
|
|
initalState(props);
|
|
|
|
}, [props, initalState]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<MobileNotSupported>
|
|
|
|
<div
|
2023-06-15 09:59:58 +00:00
|
|
|
className={classNames("scheduler-wrapper flex h-full w-full flex-col")}
|
2022-12-14 13:36:10 +00:00
|
|
|
style={
|
2023-06-09 09:38:18 +00:00
|
|
|
{
|
|
|
|
"--one-minute-height": `calc(${hourSize}px/60)`,
|
|
|
|
"--gridDefaultSize": `${hourSize}px`,
|
|
|
|
} as React.CSSProperties // This can't live in the css file because it's a dynamic value and css variable gets super
|
2022-12-14 13:36:10 +00:00
|
|
|
}>
|
2023-06-09 09:38:18 +00:00
|
|
|
{hideHeader !== true && <SchedulerHeading />}
|
2023-06-15 09:59:58 +00:00
|
|
|
{props.isLoading && <Spinner />}
|
2023-06-09 09:38:18 +00:00
|
|
|
<div
|
|
|
|
ref={container}
|
|
|
|
className="bg-default dark:bg-muted relative isolate flex h-full flex-auto flex-col">
|
2022-12-14 13:36:10 +00:00
|
|
|
<div
|
|
|
|
style={{ width: "165%" }}
|
2023-06-09 09:38:18 +00:00
|
|
|
className="flex h-full max-w-full flex-none flex-col sm:max-w-none md:max-w-full">
|
2022-12-14 13:36:10 +00:00
|
|
|
<DateValues containerNavRef={containerNav} days={days} />
|
2023-06-15 09:59:58 +00:00
|
|
|
<div className="relative flex flex-auto">
|
|
|
|
<CurrentTime />
|
2023-06-25 15:04:25 +00:00
|
|
|
<div className="bg-default dark:bg-muted ring-muted sticky border-default border-l border-r left-0 z-10 w-14 flex-none ring-1" />
|
2023-06-09 09:38:18 +00:00
|
|
|
<div
|
2023-06-22 22:25:37 +00:00
|
|
|
className="grid flex-auto grid-cols-1 grid-rows-1 [--disabled-gradient-background:#F8F9FB] [--disabled-gradient-foreground:#E6E7EB] dark:[--disabled-gradient-background:#262626] dark:[--disabled-gradient-foreground:#393939]"
|
2023-06-09 09:38:18 +00:00
|
|
|
style={{
|
|
|
|
backgroundColor: "var(--disabled-gradient-background)",
|
|
|
|
background:
|
|
|
|
"repeating-linear-gradient(-45deg, var(--disabled-gradient-background), var(--disabled-gradient-background) 2.5px, var(--disabled-gradient-foreground) 2.5px, var(--disabled-gradient-foreground) 5px)",
|
|
|
|
}}>
|
2022-12-14 13:36:10 +00:00
|
|
|
<HorizontalLines
|
|
|
|
hours={hours}
|
|
|
|
numberOfGridStopsPerCell={usersCellsStopsPerHour}
|
|
|
|
containerOffsetRef={containerOffset}
|
|
|
|
/>
|
|
|
|
<VeritcalLines days={days} />
|
|
|
|
|
2023-06-15 09:59:58 +00:00
|
|
|
<SchedulerColumns
|
|
|
|
offsetHeight={containerOffset.current?.offsetHeight}
|
|
|
|
gridStopsPerDay={numberOfGridStopsPerDay}>
|
|
|
|
{/*Loop over events per day */}
|
|
|
|
{days.map((day, i) => {
|
|
|
|
return (
|
|
|
|
<li key={day.toISOString()} className="relative" style={{ gridColumnStart: i + 1 }}>
|
|
|
|
<EventList day={day} />
|
|
|
|
{/* <BlockedList day={day} containerRef={container} /> */}
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</SchedulerColumns>
|
|
|
|
|
2022-12-14 13:36:10 +00:00
|
|
|
{/* Empty Cells */}
|
|
|
|
<SchedulerColumns
|
2023-06-09 09:38:18 +00:00
|
|
|
ref={schedulerGrid}
|
2022-12-14 13:36:10 +00:00
|
|
|
offsetHeight={containerOffset.current?.offsetHeight}
|
|
|
|
gridStopsPerDay={numberOfGridStopsPerDay}>
|
|
|
|
<>
|
|
|
|
{[...Array(days.length)].map((_, i) => (
|
|
|
|
<li
|
2023-06-09 09:38:18 +00:00
|
|
|
className="relative"
|
2022-12-14 13:36:10 +00:00
|
|
|
key={i}
|
|
|
|
style={{
|
2023-06-15 09:59:58 +00:00
|
|
|
gridRow: `1 / span ${numberOfGridStopsPerDay}`,
|
2022-12-14 13:36:10 +00:00
|
|
|
}}>
|
|
|
|
{/* While startDate < endDate: */}
|
2023-06-15 09:59:58 +00:00
|
|
|
{availableTimeslots ? (
|
|
|
|
<AvailableCellsForDay
|
|
|
|
key={days[i].toISOString()}
|
|
|
|
day={days[i].toDate()}
|
|
|
|
startHour={startHour}
|
|
|
|
availableSlots={availableTimeslots}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
{[...Array(numberOfGridStopsPerDay)].map((_, j) => {
|
|
|
|
const key = `${i}-${j}`;
|
|
|
|
return (
|
|
|
|
<EmptyCell
|
|
|
|
key={key}
|
|
|
|
day={days[i].toDate()}
|
|
|
|
gridCellIdx={j}
|
|
|
|
totalGridCells={numberOfGridStopsPerDay}
|
|
|
|
selectionLength={endHour - startHour}
|
|
|
|
startHour={startHour}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</>
|
|
|
|
)}
|
2022-12-14 13:36:10 +00:00
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
</SchedulerColumns>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</MobileNotSupported>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @todo Will be removed once we have mobile support */
|
|
|
|
const MobileNotSupported = ({ children }: { children: React.ReactNode }) => {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="flex h-full flex-col items-center justify-center sm:hidden">
|
|
|
|
<h1 className="text-2xl font-bold">Mobile not supported yet </h1>
|
2023-04-05 18:14:46 +00:00
|
|
|
<p className="text-subtle">Please use a desktop browser to view this page</p>
|
2022-12-14 13:36:10 +00:00
|
|
|
</div>
|
2023-06-09 09:38:18 +00:00
|
|
|
<div className="hidden h-full sm:block">{children}</div>
|
2022-12-14 13:36:10 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|