From 7fef683abf3865a680a1e57fb53fb2316f1a4f45 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:20:16 +0200 Subject: [PATCH] feat: Improving Booking Visibility at Month-End (#22770) * remove first weeks and add last * fix added last week * prefetch availability of next month * don't switch month * On hover show month * only show new UI in monthly view * show month tooldtip only when needed * show month on first day of month * remove isFirstDayOfNextMonth * fix prefetching next month * fix datePicker tests * preventMonthSwitching in monthly view * add tests * code clean up * code clean up * code clena up for ooo days * push first day of month * remove bg color for the month badge * fix text colour * remove not needed * use object param * revert: use object param * use object param * fix DatePicker tests --------- Co-authored-by: CarinaWolli Co-authored-by: Sean Brydon Co-authored-by: Eunjae Lee --- .../bookings/Booker/components/DatePicker.tsx | 17 +- .../bookings/Booker/components/Header.tsx | 2 +- packages/features/bookings/Booker/store.ts | 13 +- packages/features/calendars/DatePicker.tsx | 102 +++++++++-- .../calendars/__tests__/DatePicker.test.tsx | 166 ++++++++++++++++-- packages/features/embed/Embed.tsx | 4 +- .../atoms/booker/BookerPlatformWrapper.tsx | 9 +- .../atoms/booker/BookerWebWrapper.tsx | 6 +- 8 files changed, 278 insertions(+), 41 deletions(-) diff --git a/packages/features/bookings/Booker/components/DatePicker.tsx b/packages/features/bookings/Booker/components/DatePicker.tsx index 544d0d929e..1ed5f7fe39 100644 --- a/packages/features/bookings/Booker/components/DatePicker.tsx +++ b/packages/features/bookings/Booker/components/DatePicker.tsx @@ -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], @@ -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 }; @@ -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", @@ -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} @@ -137,6 +147,7 @@ export const DatePicker = ({ slots={slots} scrollToTimeSlots={scrollToTimeSlots} periodData={periodData} + isCompact={isCompact} /> ); }; diff --git a/packages/features/bookings/Booker/components/Header.tsx b/packages/features/bookings/Booker/components/Header.tsx index b76ac83e0a..238bc32281 100644 --- a/packages/features/bookings/Booker/components/Header.tsx +++ b/packages/features/bookings/Booker/components/Header.tsx @@ -130,7 +130,7 @@ export function Header({ )} diff --git a/packages/features/bookings/Booker/store.ts b/packages/features/bookings/Booker/store.ts index 3f7409caa1..0a9eb64129 100644 --- a/packages/features/bookings/Booker/store.ts +++ b/packages/features/bookings/Booker/store.ts @@ -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 @@ -192,7 +196,7 @@ export const useBookerStore = createWithEqualityFn((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"); @@ -207,7 +211,8 @@ export const useBookerStore = createWithEqualityFn((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")); @@ -264,7 +269,7 @@ export const useBookerStore = createWithEqualityFn((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) => { diff --git a/packages/features/calendars/DatePicker.tsx b/packages/features/calendars/DatePicker.tsx index 97c981b9bc..644361d724 100644 --- a/packages/features/calendars/DatePicker.tsx +++ b/packages/features/calendars/DatePicker.tsx @@ -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"; @@ -56,6 +57,8 @@ export type DatePickerProps = { }[] >; periodData?: PeriodData; + // Whether this is a compact sidebar view or main monthly view + isCompact?: boolean; }; const Day = ({ @@ -65,6 +68,8 @@ const Day = ({ away, emoji, customClassName, + showMonthTooltip, + isFirstDayOfNextMonth, ...props }: JSX.IntrinsicElements["button"] & { active: boolean; @@ -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 = (