co-authored by
Sean Brydon
parent
fcd892bfa0
commit
cf89598279
@@ -57,7 +57,7 @@ export function Header({
|
||||
return <LayoutToggleWithData />;
|
||||
}
|
||||
|
||||
const endDate = selectedDate.add(extraDays, "days");
|
||||
const endDate = selectedDate.add(extraDays - 1, "days");
|
||||
|
||||
const isSameMonth = () => {
|
||||
return selectedDate.format("MMM") === endDate.format("MMM");
|
||||
|
||||
@@ -4,7 +4,6 @@ import dayjs from "@calcom/dayjs";
|
||||
import { Calendar } from "@calcom/features/calendars/weeklyview";
|
||||
import type { CalendarAvailableTimeslots } from "@calcom/features/calendars/weeklyview/types/state";
|
||||
|
||||
import { useTimePreferences } from "../../lib/timePreferences";
|
||||
import { useBookerStore } from "../store";
|
||||
import { useEvent, useScheduleForEvent } from "../utils/event";
|
||||
|
||||
@@ -16,7 +15,6 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
|
||||
const schedule = useScheduleForEvent({
|
||||
prefetchNextMonth: !!extraDays && dayjs(date).month() !== dayjs(date).add(extraDays, "day").month(),
|
||||
});
|
||||
const { timezone } = useTimePreferences();
|
||||
|
||||
const event = useEvent();
|
||||
const eventDuration = selectedEventDuration || event?.data?.length || 30;
|
||||
@@ -28,15 +26,18 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
|
||||
|
||||
for (const day in schedule.data.slots) {
|
||||
availableTimeslots[day] = schedule.data.slots[day].map((slot) => ({
|
||||
// First formatting to LLL and then passing it to date prevents toDate()
|
||||
// from changing the timezone to users local machine (instead of itmezone selected in UI dropdown)
|
||||
start: new Date(dayjs(slot.time).utc().tz(timezone).format("LLL")),
|
||||
end: new Date(dayjs(slot.time).utc().tz(timezone).add(eventDuration, "minutes").format("LLL")),
|
||||
start: dayjs(slot.time).toDate(),
|
||||
end: dayjs(slot.time).add(eventDuration, "minutes").toDate(),
|
||||
}));
|
||||
}
|
||||
|
||||
return availableTimeslots;
|
||||
}, [schedule, timezone, eventDuration]);
|
||||
}, [schedule, eventDuration]);
|
||||
|
||||
const startDate = selectedDate ? dayjs(selectedDate).toDate() : dayjs().toDate();
|
||||
const endDate = dayjs(startDate)
|
||||
.add(extraDays - 1, "day")
|
||||
.toDate();
|
||||
|
||||
return (
|
||||
<div className="h-full [--calendar-dates-sticky-offset:66px]">
|
||||
@@ -46,9 +47,9 @@ export const LargeCalendar = ({ extraDays }: { extraDays: number }) => {
|
||||
startHour={0}
|
||||
endHour={23}
|
||||
events={[]}
|
||||
startDate={selectedDate ? new Date(selectedDate) : new Date()}
|
||||
endDate={dayjs(selectedDate).add(extraDays, "day").toDate()}
|
||||
onEmptyCellClick={(date) => setSelectedTimeslot(date.toString())}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onEmptyCellClick={(date) => setSelectedTimeslot(date.toISOString())}
|
||||
gridCellsPerHour={60 / eventDuration}
|
||||
hoverEventDuration={eventDuration}
|
||||
hideHeader
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences";
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
import { useCalendarStore } from "../state/store";
|
||||
@@ -22,6 +23,7 @@ export function Calendar(props: CalendarComponentProps) {
|
||||
const containerOffset = useRef<HTMLDivElement | null>(null);
|
||||
const schedulerGrid = useRef<HTMLOListElement | null>(null);
|
||||
const initalState = useCalendarStore((state) => state.initState);
|
||||
const { timezone } = useTimePreferences();
|
||||
|
||||
const startDate = useCalendarStore((state) => state.startDate);
|
||||
const endDate = useCalendarStore((state) => state.endDate);
|
||||
@@ -32,7 +34,11 @@ 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 hours = useMemo(
|
||||
() => getHoursToDisplay(startHour || 0, endHour || 23, timezone),
|
||||
[startHour, endHour, timezone]
|
||||
);
|
||||
const numberOfGridStopsPerDay = hours.length * usersCellsStopsPerHour;
|
||||
const hourSize = 58;
|
||||
|
||||
@@ -108,7 +114,7 @@ export function Calendar(props: CalendarComponentProps) {
|
||||
{availableTimeslots ? (
|
||||
<AvailableCellsForDay
|
||||
key={days[i].toISOString()}
|
||||
day={days[i].toDate()}
|
||||
day={days[i]}
|
||||
startHour={startHour}
|
||||
availableSlots={availableTimeslots}
|
||||
/>
|
||||
@@ -119,11 +125,12 @@ export function Calendar(props: CalendarComponentProps) {
|
||||
return (
|
||||
<EmptyCell
|
||||
key={key}
|
||||
day={days[i].toDate()}
|
||||
day={days[i]}
|
||||
gridCellIdx={j}
|
||||
totalGridCells={numberOfGridStopsPerDay}
|
||||
selectionLength={endHour - startHour}
|
||||
startHour={startHour}
|
||||
timezone={timezone}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -5,6 +5,12 @@ import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
|
||||
import { useCalendarStore } from "../../state/store";
|
||||
|
||||
function calculateMinutesFromStart(startHour: number, currentHour: number, currentMinute: number) {
|
||||
const startMinute = startHour * 60;
|
||||
const currentMinuteOfDay = currentHour * 60 + currentMinute;
|
||||
return currentMinuteOfDay - startMinute;
|
||||
}
|
||||
|
||||
export function CurrentTime() {
|
||||
const currentTimeRef = useRef<HTMLDivElement>(null);
|
||||
const [scrolledIntoView, setScrolledIntoView] = useState(false);
|
||||
@@ -13,19 +19,21 @@ export function CurrentTime() {
|
||||
startHour: state.startHour || 0,
|
||||
endHour: state.endHour || 23,
|
||||
}));
|
||||
const { timeFormat } = useTimePreferences();
|
||||
const { timeFormat, timezone } = 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();
|
||||
|
||||
const currentDateTime = dayjs().tz(timezone); // Get current date and time in the specified timezone
|
||||
|
||||
const currentHour = currentDateTime.hour();
|
||||
const currentMinute = currentDateTime.minute();
|
||||
|
||||
if (currentHour > endHour || currentHour < startHour) {
|
||||
setCurrentTimePos(null);
|
||||
}
|
||||
|
||||
const minutesFromStart = currentMinute - startHour * 60;
|
||||
const minutesFromStart = calculateMinutesFromStart(startHour, currentHour, currentMinute);
|
||||
setCurrentTimePos(minutesFromStart);
|
||||
|
||||
if (!currentTimeRef.current || scrolledIntoView) return;
|
||||
@@ -35,7 +43,7 @@ export function CurrentTime() {
|
||||
setScrolledIntoView(true);
|
||||
}, 100);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startHour, endHour, scrolledIntoView]);
|
||||
}, [startHour, endHour, scrolledIntoView, timezone]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -46,7 +54,7 @@ export function CurrentTime() {
|
||||
top: `calc(${currentTimePos}*var(--one-minute-height) + var(--calendar-offset-top))`,
|
||||
zIndex: 70,
|
||||
}}>
|
||||
<div className="w-14 pr-2 text-right">{dayjs().format(timeFormat)}</div>
|
||||
<div className="w-14 pr-2 text-right">{dayjs().tz(timezone).format(timeFormat)}</div>
|
||||
<div className="bg-inverted h-3 w-px" />
|
||||
<div className="bg-inverted h-px w-screen" />
|
||||
</div>
|
||||
|
||||
@@ -23,12 +23,12 @@ export function EmptyCell(props: EmptyCellProps) {
|
||||
totalGridCells: props.totalGridCells,
|
||||
selectionLength: props.selectionLength,
|
||||
startHour: props.startHour,
|
||||
timezone: props.timezone,
|
||||
});
|
||||
|
||||
const minuesFromStart =
|
||||
(cellToDate.toDate().getHours() - props.startHour) * 60 + cellToDate.toDate().getMinutes();
|
||||
const minuesFromStart = (cellToDate.hour() - props.startHour) * 60 + cellToDate.minute();
|
||||
|
||||
return <Cell topOffsetMinutes={minuesFromStart} timeSlot={cellToDate} />;
|
||||
return <Cell topOffsetMinutes={minuesFromStart} timeSlot={dayjs(cellToDate).tz(props.timezone)} />;
|
||||
}
|
||||
|
||||
type AvailableCellProps = {
|
||||
@@ -38,26 +38,29 @@ type AvailableCellProps = {
|
||||
};
|
||||
|
||||
export function AvailableCellsForDay({ availableSlots, day, startHour }: AvailableCellProps) {
|
||||
const { timezone } = useTimePreferences();
|
||||
|
||||
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(),
|
||||
topOffsetMinutes:
|
||||
(dayjs(slot.start).tz(timezone).hour() - startHour) * 60 + dayjs(slot.start).tz(timezone).minute(),
|
||||
})),
|
||||
[slotsForToday, startHour]
|
||||
[slotsForToday, startHour, timezone]
|
||||
);
|
||||
|
||||
if (!availableSlots) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{slots?.map((slot) => {
|
||||
{slots?.map((slot, index) => {
|
||||
return (
|
||||
<Cell
|
||||
key={slot.slot.start.toISOString()}
|
||||
timeSlot={dayjs(slot.slot.start)}
|
||||
key={index}
|
||||
timeSlot={dayjs(slot.slot.start).tz(timezone)}
|
||||
topOffsetMinutes={slot.topOffsetMinutes}
|
||||
/>
|
||||
);
|
||||
@@ -73,7 +76,8 @@ type CellProps = {
|
||||
};
|
||||
|
||||
function Cell({ isDisabled, topOffsetMinutes, timeSlot }: CellProps) {
|
||||
const timeFormat = useTimePreferences((state) => state.timeFormat);
|
||||
const { timeFormat } = useTimePreferences();
|
||||
|
||||
const { onEmptyCellClick, hoverEventDuration } = useCalendarStore(
|
||||
(state) => ({
|
||||
onEmptyCellClick: state.onEmptyCellClick,
|
||||
@@ -97,7 +101,9 @@ function Cell({ isDisabled, topOffsetMinutes, timeSlot }: CellProps) {
|
||||
overflow: "visible",
|
||||
top: topOffsetMinutes ? `calc(${topOffsetMinutes}*var(--one-minute-height))` : undefined,
|
||||
}}
|
||||
onClick={() => onEmptyCellClick && onEmptyCellClick(timeSlot.toDate())}>
|
||||
onClick={() => {
|
||||
onEmptyCellClick && onEmptyCellClick(timeSlot.toDate());
|
||||
}}>
|
||||
{!isDisabled && hoverEventDuration !== 0 && (
|
||||
<div
|
||||
className={classNames(
|
||||
|
||||
@@ -16,6 +16,7 @@ export const HorizontalLines = ({
|
||||
// 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 (
|
||||
<div
|
||||
className=" divide-default pointer-events-none relative z-[60] col-start-1 col-end-2 row-start-1 grid divide-y"
|
||||
|
||||
@@ -53,6 +53,7 @@ export type CalendarPrivateActions = {
|
||||
|
||||
export type CalendarAvailableTimeslots = {
|
||||
// Key is the date in YYYY-MM-DD format
|
||||
// start and end are ISOstring
|
||||
[key: string]: TimeRange[];
|
||||
};
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ export function weekdayDates(weekStart = 0, startDate: Date, length = 6) {
|
||||
};
|
||||
}
|
||||
export type GridCellToDateProps = {
|
||||
day: Date;
|
||||
day: dayjs.Dayjs;
|
||||
gridCellIdx: number;
|
||||
totalGridCells: number;
|
||||
selectionLength: number;
|
||||
startHour: number;
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
export function gridCellToDateTime({
|
||||
@@ -26,6 +27,7 @@ export function gridCellToDateTime({
|
||||
totalGridCells,
|
||||
selectionLength,
|
||||
startHour,
|
||||
timezone,
|
||||
}: GridCellToDateProps) {
|
||||
// endHour - startHour = selectionLength
|
||||
const minutesInSelection = (selectionLength + 1) * 60;
|
||||
@@ -34,27 +36,34 @@ export function gridCellToDateTime({
|
||||
|
||||
// Add startHour since we use StartOfDay for day props. This could be improved by changing the getDaysBetweenDates function
|
||||
// To handle the startHour+endHour
|
||||
const cellDateTime = dayjs(day).startOf("day").add(minutesIntoSelection, "minutes").add(startHour, "hours");
|
||||
const cellDateTime = dayjs(day)
|
||||
.tz(timezone)
|
||||
.startOf("day")
|
||||
.add(minutesIntoSelection, "minutes")
|
||||
.add(startHour, "hours");
|
||||
return cellDateTime;
|
||||
}
|
||||
|
||||
export function getDaysBetweenDates(dateFrom: Date, dateTo: Date) {
|
||||
const dates = []; // this is as dayjs date
|
||||
let startDate = dayjs(dateFrom).utc().hour(0).minute(0).second(0).millisecond(0);
|
||||
let startDate = dayjs(dateFrom).hour(0).minute(0).second(0).millisecond(0);
|
||||
|
||||
dates.push(startDate);
|
||||
const endDate = dayjs(dateTo).utc().hour(0).minute(0).second(0).millisecond(0);
|
||||
const endDate = dayjs(dateTo).hour(0).minute(0).second(0).millisecond(0);
|
||||
|
||||
while (startDate.isBefore(endDate)) {
|
||||
dates.push(startDate.add(1, "day"));
|
||||
startDate = startDate.add(1, "day");
|
||||
}
|
||||
return dates;
|
||||
|
||||
return dates.slice(0, 7);
|
||||
}
|
||||
|
||||
export function getHoursToDisplay(startHour: number, endHour: number) {
|
||||
export function getHoursToDisplay(startHour: number, endHour: number, timezone?: string) {
|
||||
const dates = []; // this is as dayjs date
|
||||
let startDate = dayjs("1970-01-01").utc().hour(startHour);
|
||||
let startDate = dayjs("1970-01-01").tz(timezone).hour(startHour);
|
||||
dates.push(startDate);
|
||||
const endDate = dayjs("1970-01-01").utc().hour(endHour);
|
||||
const endDate = dayjs("1970-01-01").tz(timezone).hour(endHour);
|
||||
while (startDate.isBefore(endDate)) {
|
||||
dates.push(startDate.add(1, "hour"));
|
||||
startDate = startDate.add(1, "hour");
|
||||
|
||||
Reference in New Issue
Block a user