+
setSelectedTimeslot(date.toString())}
- gridCellsPerHour={2}
- hoverEventDuration={30}
+ gridCellsPerHour={60 / eventDuration}
+ hoverEventDuration={eventDuration}
hideHeader
/>
diff --git a/packages/features/bookings/Booker/config.ts b/packages/features/bookings/Booker/config.ts
index 80eb11c6f4..9c19bdd313 100644
--- a/packages/features/bookings/Booker/config.ts
+++ b/packages/features/bookings/Booker/config.ts
@@ -87,8 +87,8 @@ export const resizeAnimationConfig: ResizeAnimationConfig = {
week_view: {
default: {
width: "100vw",
- minHeight: "450px",
- height: "100vh",
+ minHeight: "100vh",
+ height: "auto",
gridTemplateAreas: `
"meta header header"
"meta main main"
@@ -100,8 +100,8 @@ export const resizeAnimationConfig: ResizeAnimationConfig = {
column_view: {
default: {
width: "100vw",
- minHeight: "450px",
- height: "100vh",
+ minHeight: "100vh",
+ height: "auto",
gridTemplateAreas: `
"meta header header"
"meta main main"
diff --git a/packages/features/calendars/weeklyview/components/Calendar.tsx b/packages/features/calendars/weeklyview/components/Calendar.tsx
index 5b808d2606..ebf007f568 100644
--- a/packages/features/calendars/weeklyview/components/Calendar.tsx
+++ b/packages/features/calendars/weeklyview/components/Calendar.tsx
@@ -1,15 +1,19 @@
-import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
+import React, { useEffect, useMemo, useRef } from "react";
+
+import { classNames } from "@calcom/lib";
import { useCalendarStore } from "../state/store";
import "../styles/styles.css";
import type { CalendarComponentProps } from "../types/state";
-import { calculateHourSizeInPx, getDaysBetweenDates, getHoursToDisplay } from "../utils";
+import { getDaysBetweenDates, getHoursToDisplay } from "../utils";
import { DateValues } from "./DateValues";
-import { EmptyCell } from "./event/Empty";
+import { CurrentTime } from "./currentTime";
+import { AvailableCellsForDay, EmptyCell } from "./event/Empty";
import { EventList } from "./event/EventList";
import { SchedulerColumns } from "./grid";
import { SchedulerHeading } from "./heading/SchedulerHeading";
import { HorizontalLines } from "./horizontalLines";
+import { Spinner } from "./spinner/Spinner";
import { VeritcalLines } from "./verticalLines";
export function Calendar(props: CalendarComponentProps) {
@@ -28,26 +32,9 @@ export function Calendar(props: CalendarComponentProps) {
const hideHeader = useCalendarStore((state) => state.hideHeader);
const days = useMemo(() => getDaysBetweenDates(startDate, endDate), [startDate, endDate]);
-
const hours = useMemo(() => getHoursToDisplay(startHour || 0, endHour || 23), [startHour, endHour]);
-
const numberOfGridStopsPerDay = hours.length * usersCellsStopsPerHour;
- const [hourSize, setHourSize] = useState(calculateHourSizeInPx(schedulerGrid?.current, startHour, endHour));
-
- // Reset one hour size on window resize.
- useLayoutEffect(() => {
- const onResize = () => {
- setHourSize(calculateHourSizeInPx(schedulerGrid?.current, startHour, endHour));
- };
- window.addEventListener("resize", onResize);
-
- // By running this set function also one time in the uselayouteffect, instead of
- // only in the default value of useState, we make sure that the ref is rendered
- // to the screen and we read the correct value.
- setHourSize(calculateHourSizeInPx(schedulerGrid?.current, startHour, endHour));
-
- return () => window.removeEventListener("resize", onResize);
- }, [startHour, endHour]);
+ const hourSize = 58;
// Initalise State on inital mount
useEffect(() => {
@@ -57,7 +44,7 @@ export function Calendar(props: CalendarComponentProps) {
return (
{hideHeader !== true &&
}
+ {props.isLoading &&
}
@@ -72,13 +60,8 @@ export function Calendar(props: CalendarComponentProps) {
style={{ width: "165%" }}
className="flex h-full max-w-full flex-none flex-col sm:max-w-none md:max-w-full">
- {/* TODO: Implement this at a later date. */}
- {/*
*/}
-
+
+
- {/* Empty Cells */}
-
- <>
- {[...Array(days.length)].map((_, i) => (
-
- {/* While startDate < endDate: */}
- {[...Array(numberOfGridStopsPerDay)].map((_, j) => {
- const key = `${i}-${j}`;
- return (
-
- );
- })}
-
- ))}
- >
-
-
@@ -141,6 +90,49 @@ export function Calendar(props: CalendarComponentProps) {
);
})}
+
+ {/* Empty Cells */}
+
+ <>
+ {[...Array(days.length)].map((_, i) => (
+
+ {/* While startDate < endDate: */}
+ {availableTimeslots ? (
+
+ ) : (
+ <>
+ {[...Array(numberOfGridStopsPerDay)].map((_, j) => {
+ const key = `${i}-${j}`;
+ return (
+
+ );
+ })}
+ >
+ )}
+
+ ))}
+ >
+
diff --git a/packages/features/calendars/weeklyview/components/DateValues/index.tsx b/packages/features/calendars/weeklyview/components/DateValues/index.tsx
index c104b8abb0..5a38b4568f 100644
--- a/packages/features/calendars/weeklyview/components/DateValues/index.tsx
+++ b/packages/features/calendars/weeklyview/components/DateValues/index.tsx
@@ -12,7 +12,7 @@ export function DateValues({ days, containerNavRef }: Props) {
return (
+ className="bg-default dark:bg-muted border-b-subtle sticky top-[var(--calendar-dates-sticky-offset,0px)] z-[80] flex-none border-b sm:pr-8">
{days.map((day) => {
const isToday = dayjs().isSame(day, "day");
diff --git a/packages/features/calendars/weeklyview/components/currentTime/index.tsx b/packages/features/calendars/weeklyview/components/currentTime/index.tsx
index 962efe299c..e47a531be0 100644
--- a/packages/features/calendars/weeklyview/components/currentTime/index.tsx
+++ b/packages/features/calendars/weeklyview/components/currentTime/index.tsx
@@ -1,43 +1,54 @@
-import React, { useEffect, useState } from "react";
+import { useEffect, useState, useRef } from "react";
import dayjs from "@calcom/dayjs";
+import { useTimePreferences } from "@calcom/features/bookings/lib";
import { useCalendarStore } from "../../state/store";
-type Props = {
- containerNavRef: React.RefObject
;
- containerRef: React.RefObject;
- containerOffsetRef: React.RefObject;
-};
-
-export function CurrentTime({ containerOffsetRef }: Props) {
- const [currentTimePos, setCurrentTimePos] = useState(0);
+export function CurrentTime() {
+ const currentTimeRef = useRef(null);
+ const [scrolledIntoView, setScrolledIntoView] = useState(false);
+ const [currentTimePos, setCurrentTimePos] = useState(null);
const { startHour, endHour } = useCalendarStore((state) => ({
startHour: state.startHour || 0,
endHour: state.endHour || 23,
}));
+ const { timeFormat } = useTimePreferences();
useEffect(() => {
// Set the container scroll position based on the current time.
+ const currentHour = new Date().getHours();
let currentMinute = new Date().getHours() * 60;
currentMinute = currentMinute + new Date().getMinutes();
- if (containerOffsetRef.current) {
- const totalHours = endHour - startHour;
- const currentTimePos = currentMinute;
- setCurrentTimePos(currentTimePos);
+ if (currentHour > endHour || currentHour < startHour) {
+ setCurrentTimePos(null);
}
+
+ const minutesFromStart = currentMinute - startHour * 60;
+ setCurrentTimePos(minutesFromStart);
+
+ if (!currentTimeRef.current || scrolledIntoView) return;
+ // Within a small timeout so element has time to render.
+ setTimeout(() => {
+ currentTimeRef?.current?.scrollIntoView({ block: "center" });
+ setScrolledIntoView(true);
+ }, 100);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [startHour, endHour]);
+ }, [startHour, endHour, scrolledIntoView]);
return (
- {dayjs().format("HH:mm")}
-
-
+ style={{
+ top: `calc(${currentTimePos}*var(--one-minute-height) + var(--calendar-offset-top))`,
+ zIndex: 70,
+ }}>
+
{dayjs().format(timeFormat)}
+
+
);
}
diff --git a/packages/features/calendars/weeklyview/components/event/Empty.tsx b/packages/features/calendars/weeklyview/components/event/Empty.tsx
index 460e4e3e5b..0c9d63a883 100644
--- a/packages/features/calendars/weeklyview/components/event/Empty.tsx
+++ b/packages/features/calendars/weeklyview/components/event/Empty.tsx
@@ -1,5 +1,7 @@
+import { useMemo } from "react";
import { shallow } from "zustand/shallow";
+import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib";
import { classNames } from "@calcom/lib";
@@ -10,10 +12,67 @@ import type { GridCellToDateProps } from "../../utils";
import { gridCellToDateTime } from "../../utils";
type EmptyCellProps = GridCellToDateProps & {
- availableSlots?: CalendarAvailableTimeslots;
+ isDisabled?: boolean;
+ topOffsetMinutes?: number;
};
export function EmptyCell(props: EmptyCellProps) {
+ const cellToDate = gridCellToDateTime({
+ day: props.day,
+ gridCellIdx: props.gridCellIdx,
+ totalGridCells: props.totalGridCells,
+ selectionLength: props.selectionLength,
+ startHour: props.startHour,
+ });
+
+ const minuesFromStart =
+ (cellToDate.toDate().getHours() - props.startHour) * 60 + cellToDate.toDate().getMinutes();
+
+ return | ;
+}
+
+type AvailableCellProps = {
+ availableSlots: CalendarAvailableTimeslots;
+ day: GridCellToDateProps["day"];
+ startHour: GridCellToDateProps["startHour"];
+};
+
+export function AvailableCellsForDay({ availableSlots, day, startHour }: AvailableCellProps) {
+ const slotsForToday = availableSlots && availableSlots[dayjs(day).format("YYYY-MM-DD")];
+
+ const slots = useMemo(
+ () =>
+ slotsForToday?.map((slot) => ({
+ slot,
+ topOffsetMinutes: (slot.start.getHours() - startHour) * 60 + slot.start.getMinutes(),
+ })),
+ [slotsForToday, startHour]
+ );
+
+ if (!availableSlots) return null;
+
+ return (
+ <>
+ {slots?.map((slot) => {
+ return (
+ |
+ );
+ })}
+ >
+ );
+}
+
+type CellProps = {
+ isDisabled?: boolean;
+ topOffsetMinutes?: number;
+ timeSlot: Dayjs;
+};
+
+function Cell({ isDisabled, topOffsetMinutes, timeSlot }: CellProps) {
const timeFormat = useTimePreferences((state) => state.timeFormat);
const { onEmptyCellClick, hoverEventDuration } = useCalendarStore(
(state) => ({
@@ -23,46 +82,37 @@ export function EmptyCell(props: EmptyCellProps) {
shallow
);
- const cellToDate = gridCellToDateTime({
- day: props.day,
- gridCellIdx: props.gridCellIdx,
- totalGridCells: props.totalGridCells,
- selectionLength: props.selectionLength,
- startHour: props.startHour,
- });
-
- // Empty cell won't show it self (be disabled) when
- // availableslots is passed in, and it's curren time is not part of the available slots
- const isDisabled =
- props.availableSlots &&
- !props.availableSlots[dayjs(props.day).format("YYYY-MM-DD")]?.find(
- (slot) => slot.start.getTime() === cellToDate.toDate().getTime()
- );
-
return (
onEmptyCellClick && onEmptyCellClick(cellToDate.toDate())}>
+ data-slot={timeSlot.toISOString()}
+ style={{
+ height: `calc(${hoverEventDuration}*var(--one-minute-height))`,
+ overflow: "visible",
+ top: topOffsetMinutes ? `calc(${topOffsetMinutes}*var(--one-minute-height))` : undefined,
+ }}
+ onClick={() => onEmptyCellClick && onEmptyCellClick(timeSlot.toDate())}>
{!isDisabled && hoverEventDuration !== 0 && (
15 && "items-start pt-3",
+ hoverEventDuration && hoverEventDuration < 15 && "items-center"
+ )}
style={{
height: `calc(${hoverEventDuration}*var(--one-minute-height) - 2px)`,
- zIndex: 49,
+ zIndex: 80,
// @TODO: This used to be 90% as per Sean's work. I think this was needed when
// multiple events are stacked next to each other. We might need to add this back later.
width: "calc(100% - 2px)",
}}>
-
{cellToDate.format(timeFormat)}
+
{timeSlot.format(timeFormat)}
)}
diff --git a/packages/features/calendars/weeklyview/components/horizontalLines/index.tsx b/packages/features/calendars/weeklyview/components/horizontalLines/index.tsx
index f549212059..a1865c5079 100644
--- a/packages/features/calendars/weeklyview/components/horizontalLines/index.tsx
+++ b/packages/features/calendars/weeklyview/components/horizontalLines/index.tsx
@@ -1,6 +1,7 @@
import { useId } from "react";
import type dayjs from "@calcom/dayjs";
+import { useTimePreferences } from "@calcom/features/bookings/lib";
export const HorizontalLines = ({
hours,
@@ -11,7 +12,9 @@ export const HorizontalLines = ({
numberOfGridStopsPerCell: number;
containerOffsetRef: React.RefObject;
}) => {
- const finalHour = hours[hours.length - 1].add(1, "hour").format("h A");
+ const { timeFormat } = useTimePreferences();
+ // We need to force the minute to zero, because otherwise in ex GMT+5.5, it would show :30 minute times (but at the positino of :00)
+ const finalHour = hours[hours.length - 1].add(1, "hour").minute(0).format(timeFormat);
const id = useId();
return (
-
+
{hours.map((hour) => (
- <>
-
-
- {hour.format("h A")}
-
+
+
+ {/* We need to force the minute to zero, because otherwise in ex GMT+5.5, it would show :30 minute times (but at the positino of :00) */}
+ {hour.minute(0).format(timeFormat)}
- >
+
))}
diff --git a/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx b/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx
new file mode 100644
index 0000000000..b8826b9937
--- /dev/null
+++ b/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx
@@ -0,0 +1,15 @@
+export const Spinner = () => (
+
+);
diff --git a/packages/features/calendars/weeklyview/styles/styles.css b/packages/features/calendars/weeklyview/styles/styles.css
index 1c4de29ae3..3a28c2c150 100644
--- a/packages/features/calendars/weeklyview/styles/styles.css
+++ b/packages/features/calendars/weeklyview/styles/styles.css
@@ -3,6 +3,7 @@
--hoursInDay: 24;
--minuteInHour:60;
--gridMobileSize: 100vw;
+ --calendar-offset-top: 28px;
}
.scheduler-grid-row-template{
diff --git a/packages/features/calendars/weeklyview/types/state.ts b/packages/features/calendars/weeklyview/types/state.ts
index cc8f6062e6..08a6a64407 100644
--- a/packages/features/calendars/weeklyview/types/state.ts
+++ b/packages/features/calendars/weeklyview/types/state.ts
@@ -120,6 +120,6 @@ export type CalendarState = {
hideHeader?: boolean;
};
-export type CalendarComponentProps = CalendarPublicActions & CalendarState;
+export type CalendarComponentProps = CalendarPublicActions & CalendarState & { isLoading?: boolean };
export type CalendarStoreProps = CalendarComponentProps & CalendarPrivateActions;