Files
calendar/apps/web/modules/bookings/hooks/useBookingListData.ts
T
Eunjae LeeandGitHub 3986d61f40 feat(bookings): improve bookings redesign (#25251)
* use booking.uid instead of booking.id for url param

* show timezone on calendar

* fix type

* restore horizontal tab and remove header and subtitle

* clean up sidebar items

* fix event propagation from attendees

* fetch all statuses except for cancelled on calendar view

* clean up styles of the badges on BookingListItem

* fix useMediaQuery

* add close button to the header

* add assignment reason to the details sheet

* use separator row

* use ToggleGroup for the top bookings tab

* move ViewToggleButton

* resize the action button

* remove wrong prop

* fix type error

* fix type error

* hide view toggle button on mobile (and fix the breakpoint)

* remove unused e2e tests

* fix e2e tests

* hide toggle button when feature flag is off

* update skeleton

* improve attendees on booking list item and slide over

* improve attendee dropdown

* fix type error

* move query to containers

* select attendee email

* infinite fetching for calendar view

* update styles

* fix compatibility

* fix: add backward compatibility for status field in getAllUserBookings

* increase calendar height

* fix type error

* support Member filter only for admin / owners

* add debug log (TEMP)

* add event border color

* show Reject / Accept buttons on BookingDetailsSheet

* move description section to the top

* update When section

* update style of Who section

* add CancelBookingDialog WIP

* fix CancelBookingDialog

* increase clickable area

* add schedule info section WIP

* fix flaky reject button

* fixing reschedule info WIP

* add fromReschedule index to Booking

* improve rescheduled information

* improve reassignment

* fix type error

* fix unit test

* respect user's weekStart value on the booking calendar view

* update debug log

* improve payment section

* clean up

* fix log message

* reposition filters on list view

* fix bookings controller api2 e2e test

* clean up file by extracting logic into custom hooks

* rename files

* merge BookingCalendar into its container

* extract logic into separate hook files

* remove redundant logic

* rearrange items on calendar view

* add WeekPicker

* extract filter button

* responsive header on list view

* horizontal scroll for ToggleGroup WIP

* fix type error

* fix cancelling recurring event

* address feedback

* fix e2e tests

* fix unit test

* fix e2e tests

* make hover style more visible for ToggleGroup

* fix margin on CancelBookingDialog

* update styles on the slide over (mostly font weight)

* update style of CancelBookingDialog

* update styles

* update margin top for the header

* refactor getBookingDetails handler

* fix gap in who section

* auto-filter the current user on the calendar view

* calculate calendar height considering top banners

* improve booking details sheet interaction without overlay

* update calendar event styles

* update reject dialog style

* put uid first in the query params

* fix class name

* memoize functions in useMediaQuery

* query attendee with id instead of email

* update margins

* replace TRPCError with ErrorWithCode

* move calculation outside loop

* remove dead code
2025-12-10 13:40:04 +00:00

116 lines
4.1 KiB
TypeScript

import { useMemo } from "react";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type {
RowData,
BookingRowData,
BookingListingStatus,
BookingOutput,
BookingsGetOutput,
} from "../types";
/**
* Transform raw bookings into final data structure with separators
* - Deduplicates recurring bookings for recurring/unconfirmed/cancelled tabs
* - For "upcoming" status, organizes into "Today" and "Next" sections
*/
export function useBookingListData({
data,
status,
userTimeZone,
}: {
data?: BookingsGetOutput;
status: BookingListingStatus;
userTimeZone?: string;
}) {
const { t } = useLocale();
/**
* Transform raw bookings into flat list (excluding today's bookings for "upcoming" status)
* - Deduplicates recurring bookings for recurring/unconfirmed/cancelled tabs
* - For "upcoming" status, filters out today's bookings (they're shown in separate "Today" section)
*/
const flatData = useMemo<BookingRowData[]>(() => {
const todayDateString = dayjs().tz(userTimeZone).format("YYYY-MM-DD");
// For recurring/unconfirmed/cancelled tabs: track recurring series to show only one representative booking per series
// Key: recurringEventId, Value: array of all bookings in that series
const shownBookings: Record<string, BookingOutput[]> = {};
const filterBookings = (booking: BookingOutput) => {
// Deduplicate recurring bookings for specific status tabs
// This ensures we show only ONE booking per recurring series instead of all occurrences
if (status === "recurring" || status == "unconfirmed" || status === "cancelled") {
// Non-recurring bookings are always shown
if (!booking.recurringEventId) {
return true;
}
// If we've already encountered this recurring series
if (
shownBookings[booking.recurringEventId] !== undefined &&
shownBookings[booking.recurringEventId].length > 0
) {
// Store this occurrence but DON'T display it (return false to filter out)
shownBookings[booking.recurringEventId].push(booking);
return false;
}
// First occurrence of this recurring series - show it and start tracking
shownBookings[booking.recurringEventId] = [booking];
} else if (status === "upcoming") {
// For "upcoming" tab, exclude today's bookings (they're shown separately in the "Today" section)
return dayjs(booking.startTime).tz(userTimeZone).format("YYYY-MM-DD") !== todayDateString;
}
return true;
};
return (
data?.bookings.filter(filterBookings).map((booking) => ({
type: "data" as const,
booking,
recurringInfo: data?.recurringInfo.find((info) => info.recurringEventId === booking.recurringEventId),
isToday: false,
})) || []
);
}, [data, status, userTimeZone]);
// Extract today's bookings for the "Today" section (only used in "upcoming" status)
const bookingsToday = useMemo<BookingRowData[]>(() => {
const todayDateString = dayjs().tz(userTimeZone).format("YYYY-MM-DD");
return (data?.bookings ?? [])
.filter(
(booking: BookingOutput) =>
dayjs(booking.startTime).tz(userTimeZone).format("YYYY-MM-DD") === todayDateString
)
.map((booking) => ({
type: "data" as const,
booking,
recurringInfo: data?.recurringInfo.find((info) => info.recurringEventId === booking.recurringEventId),
isToday: true,
}));
}, [data, userTimeZone]);
// Combine data with section separators for "upcoming" tab
const finalData = useMemo<RowData[]>(() => {
// For other statuses, just return the flat list
if (status !== "upcoming") {
return flatData;
}
// For "upcoming" status, organize into "Today" and "Next" sections
const merged: RowData[] = [];
if (bookingsToday.length > 0) {
merged.push({ type: "separator" as const, label: t("today") }, ...bookingsToday);
}
if (flatData.length > 0) {
merged.push({ type: "separator" as const, label: t("next") }, ...flatData);
}
return merged;
}, [bookingsToday, flatData, status, t]);
return finalData;
}