Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions packages/features/bookings/Booker/components/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ export const DatePicker = ({
scrollToTimeSlots?: () => void;
}) => {
const { i18n } = useLocale();
const [month, selectedDate] = useBookerStore((state) => [state.month, state.selectedDate], shallow);
const [month, selectedDate, layout] = useBookerStore(
(state) => [state.month, state.selectedDate, state.layout],
shallow
);

const [setSelectedDate, setMonth, setDayCount] = useBookerStore(
(state) => [state.setSelectedDate, state.setMonth, state.setDayCount],
Expand All @@ -83,7 +86,7 @@ export const DatePicker = ({

const onMonthChange = (date: Dayjs) => {
setMonth(date.format("YYYY-MM"));
setSelectedDate(date.format("YYYY-MM-DD"));
setSelectedDate({ date: date.format("YYYY-MM-DD") });
setDayCount(null); // Whenever the month is changed, we nullify getting X days
};

Expand All @@ -98,6 +101,9 @@ export const DatePicker = ({
});
moveToNextMonthOnNoAvailability();

// Determine if this is a compact sidebar view based on layout
const isCompact = layout !== "month_view";

const periodData: PeriodData = {
...{
periodType: "UNLIMITED",
Expand Down Expand Up @@ -126,7 +132,11 @@ export const DatePicker = ({
className={classNames?.datePickerContainer}
isLoading={isLoading}
onChange={(date: Dayjs | null, omitUpdatingParams?: boolean) => {
setSelectedDate(date === null ? date : date.format("YYYY-MM-DD"), omitUpdatingParams);
setSelectedDate({
date: date === null ? date : date.format("YYYY-MM-DD"),
omitUpdatingParams,
preventMonthSwitching: !isCompact, // Prevent month switching when in monthly view
});
}}
onMonthChange={onMonthChange}
includedDates={nonEmptyScheduleDays}
Expand All @@ -137,6 +147,7 @@ export const DatePicker = ({
slots={slots}
scrollToTimeSlots={scrollToTimeSlots}
periodData={periodData}
isCompact={isCompact}
/>
);
};
2 changes: 1 addition & 1 deletion packages/features/bookings/Booker/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function Header({
<Button
className="capitalize ltr:ml-2 rtl:mr-2"
color="secondary"
onClick={() => setSelectedDate(today.format("YYYY-MM-DD"))}>
onClick={() => setSelectedDate({ date: today.format("YYYY-MM-DD") })}>
{t("today")}
</Button>
)}
Expand Down
13 changes: 9 additions & 4 deletions packages/features/bookings/Booker/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ export type BookerStore = {
* Date selected by user (exact day). Format is YYYY-MM-DD.
*/
selectedDate: string | null;
setSelectedDate: (date: string | null, omitUpdatingParams?: boolean) => void;
setSelectedDate: (params: {
date: string | null;
omitUpdatingParams?: boolean;
preventMonthSwitching?: boolean;
}) => void;
addToSelectedDate: (days: number) => void;
/**
* Multiple Selected Dates and Times
Expand Down Expand Up @@ -192,7 +196,7 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
return set({ layout });
},
selectedDate: getQueryParam("date") || null,
setSelectedDate: (selectedDate: string | null, omitUpdatingParams = false) => {
setSelectedDate: ({ date: selectedDate, omitUpdatingParams = false, preventMonthSwitching = false }) => {
// unset selected date
if (!selectedDate) {
removeQueryParam("date");
Expand All @@ -207,7 +211,8 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
}

// Setting month make sure small calendar in fullscreen layouts also updates.
if (newSelection.month() !== currentSelection.month()) {
// preventMonthSwitching is true in monthly view
if (!preventMonthSwitching && newSelection.month() !== currentSelection.month()) {
set({ month: newSelection.format("YYYY-MM") });
if (!omitUpdatingParams && (!get().isPlatform || get().allowUpdatingUrlParams)) {
updateQueryParam("month", newSelection.format("YYYY-MM"));
Expand Down Expand Up @@ -264,7 +269,7 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
if (!get().isPlatform || get().allowUpdatingUrlParams) {
updateQueryParam("month", month ?? "");
}
get().setSelectedDate(null);
get().setSelectedDate({ date: null });
},
dayCount: BOOKER_NUMBER_OF_DAYS_TO_LOAD > 0 ? BOOKER_NUMBER_OF_DAYS_TO_LOAD : null,
setDayCount: (dayCount: number | null) => {
Expand Down
102 changes: 89 additions & 13 deletions packages/features/calendars/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { PeriodData } from "@calcom/types/Event";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { SkeletonText } from "@calcom/ui/components/skeleton";
import { Tooltip } from "@calcom/ui/components/tooltip";

import NoAvailabilityDialog from "./NoAvailabilityDialog";

Expand Down Expand Up @@ -56,6 +57,8 @@ export type DatePickerProps = {
}[]
>;
periodData?: PeriodData;
// Whether this is a compact sidebar view or main monthly view
isCompact?: boolean;
};

const Day = ({
Expand All @@ -65,6 +68,8 @@ const Day = ({
away,
emoji,
customClassName,
showMonthTooltip,
isFirstDayOfNextMonth,
...props
}: JSX.IntrinsicElements["button"] & {
active: boolean;
Expand All @@ -75,12 +80,14 @@ const Day = ({
dayContainer?: string;
dayActive?: string;
};
showMonthTooltip?: boolean;
isFirstDayOfNextMonth?: boolean;
}) => {
const { t } = useLocale();
const enabledDateButtonEmbedStyles = useEmbedStyles("enabledDateButton");
const disabledDateButtonEmbedStyles = useEmbedStyles("disabledDateButton");

return (
const buttonContent = (
<button
type="button"
style={disabled ? { ...disabledDateButtonEmbedStyles } : { ...enabledDateButtonEmbedStyles }}
Expand Down Expand Up @@ -113,6 +120,33 @@ const Day = ({
)}
</button>
);

const content = showMonthTooltip ? (
<Tooltip content={date.format("MMMM")}>{buttonContent}</Tooltip>
) : (
buttonContent
);

return (
<>
{isFirstDayOfNextMonth && (
<div
className={classNames(
"absolute top-0 z-10 mx-auto w-fit rounded-full font-semibold uppercase tracking-wide",
active ? "text-white" : "text-default",
disabled && "bg-emphasis"
)}
style={{
fontSize: "10px",
lineHeight: "13px",
padding: disabled ? "0 3px" : "3px 3px 3px 4px",
}}>
{date.format("MMM")}
</div>
)}
{content}
</>
);
};

const Days = ({
Expand All @@ -129,6 +163,7 @@ const Days = ({
customClassName,
isBookingInPast,
periodData,
isCompact,
...props
}: Omit<DatePickerProps, "locale" | "className" | "weekStart"> & {
DayComponent?: React.FC<React.ComponentProps<typeof Day>>;
Expand All @@ -143,20 +178,48 @@ const Days = ({
scrollToTimeSlots?: () => void;
isBookingInPast: boolean;
periodData: PeriodData;
isCompact?: boolean;
}) => {
// Create placeholder elements for empty days in first week
const weekdayOfFirst = browsingDate.date(1).day();

const includedDates = getAvailableDatesInMonth({
browsingDate: browsingDate.toDate(),
minDate,
includedDates: props.includedDates,
});

const days: (Dayjs | null)[] = Array((weekdayOfFirst - weekStart + 7) % 7).fill(null);
for (let day = 1, dayCount = daysInMonth(browsingDate); day <= dayCount; day++) {
const date = browsingDate.set("date", day);
days.push(date);
const today = dayjs();
const firstDayOfMonth = browsingDate.startOf("month");
const isSecondWeekOver = today.isAfter(firstDayOfMonth.add(2, "week"));
let days: (Dayjs | null)[] = [];

const getPadding = (day: number) => (browsingDate.set("date", day).day() - weekStart + 7) % 7;
const totalDays = daysInMonth(browsingDate);

// Only apply end-of-month logic for main monthly view (not compact sidebar)
if (isSecondWeekOver && !isCompact) {
const startDay = 8;
const pad = getPadding(startDay);
days = Array(pad).fill(null);

for (let day = startDay; day <= totalDays; day++) {
days.push(browsingDate.set("date", day));
}

const remainingInRow = days.length % 7;
const extraDays = (remainingInRow > 0 ? 7 - remainingInRow : 0) + 7;
const nextMonth = browsingDate.add(1, "month");

// Add days starting from day 1 of next month
for (let i = 0; i < extraDays; i++) {
days.push(nextMonth.set("date", 1 + i));
}
} else {
// Traditional calendar grid logic for compact sidebar or early in month
const pad = getPadding(1);
days = Array(pad).fill(null);

for (let day = 1; day <= totalDays; day++) {
days.push(browsingDate.set("date", day));
}
}

const [selectedDatesAndTimes] = useBookerStore((state) => [state.selectedDatesAndTimes], shallow);
Expand Down Expand Up @@ -188,20 +251,29 @@ const Days = ({

const daysToRenderForTheMonth = days.map((day) => {
if (!day) return { day: null, disabled: true };

const dateKey = yyyymmdd(day);
const oooInfo = slots && slots?.[dateKey] ? slots?.[dateKey]?.find((slot) => slot.away) : null;
const daySlots = slots?.[dateKey] || [];
const oooInfo = daySlots.find((slot) => slot.away) || null;

const isNextMonth = day.month() !== browsingDate.month();
const isFirstDayOfNextMonth = isSecondWeekOver && !isCompact && isNextMonth && day.date() === 1;

const included = includedDates?.includes(dateKey);
const excluded = excludedDates.includes(dateKey);

const isOOOAllDay = !!(slots && slots[dateKey] && slots[dateKey].every((slot) => slot.away));
const hasAvailableSlots = daySlots.some((slot) => !slot.away);
const isOOOAllDay = daySlots.length > 0 && daySlots.every((slot) => slot.away);
const away = isOOOAllDay;
const disabled = away ? !oooInfo?.toUser : !included || excluded;

const disabled = away ? !oooInfo?.toUser : isNextMonth ? !hasAvailableSlots : !included || excluded;

return {
day: day,
day,
disabled,
away,
emoji: oooInfo?.emoji,
isFirstDayOfNextMonth,
};
});

Expand Down Expand Up @@ -239,7 +311,7 @@ const Days = ({

return (
<>
{daysToRenderForTheMonth.map(({ day, disabled, away, emoji }, idx) => (
{daysToRenderForTheMonth.map(({ day, disabled, away, emoji, isFirstDayOfNextMonth }, idx) => (
<div key={day === null ? `e-${idx}` : `day-${day.format()}`} className="relative w-full pt-[100%]">
{day === null ? (
<div key={`e-${idx}`} />
Expand All @@ -265,6 +337,8 @@ const Days = ({
active={isActive(day)}
away={away}
emoji={emoji}
showMonthTooltip={isSecondWeekOver && !isCompact}
isFirstDayOfNextMonth={isFirstDayOfNextMonth}
/>
)}
</div>
Expand Down Expand Up @@ -297,6 +371,7 @@ const DatePicker = ({
periodDays: null,
periodType: "UNLIMITED",
},
isCompact,
...passThroughProps
}: DatePickerProps &
Partial<React.ComponentProps<typeof Days>> & {
Expand Down Expand Up @@ -406,6 +481,7 @@ const DatePicker = ({
includedDates={includedDates}
isBookingInPast={isBookingInPast}
periodData={periodData}
isCompact={isCompact}
/>
</div>
</div>
Expand Down
Loading
Loading