diff --git a/apps/web/modules/bookings/columns/filterColumns.ts b/apps/web/modules/bookings/columns/filterColumns.ts
new file mode 100644
index 0000000000..7ed2b73275
--- /dev/null
+++ b/apps/web/modules/bookings/columns/filterColumns.ts
@@ -0,0 +1,133 @@
+import type { VisibilityState } from "@tanstack/react-table";
+import { createColumnHelper } from "@tanstack/react-table";
+
+import { ColumnFilterType } from "@calcom/features/data-table";
+
+import type { RowData } from "../types";
+
+interface BuildFilterColumnsParams {
+ t: (key: string) => string;
+ permissions: {
+ canReadOthersBookings: boolean;
+ };
+ status: string;
+}
+
+/**
+ * IDs of filter columns that should be hidden in the UI
+ * These columns are used for filtering but not displayed
+ */
+const FILTER_COLUMN_IDS = [
+ "eventTypeId",
+ "teamId",
+ "userId",
+ "attendeeName",
+ "attendeeEmail",
+ "dateRange",
+ "bookingUid",
+] as const;
+
+/**
+ * Gets the column visibility state for filter columns
+ * All filter columns are hidden by default as they only provide filtering functionality
+ */
+export function getFilterColumnVisibility(): VisibilityState {
+ return Object.fromEntries(FILTER_COLUMN_IDS.map((id) => [id, false]));
+}
+
+export function buildFilterColumns({ t, permissions, status }: BuildFilterColumnsParams) {
+ const columnHelper = createColumnHelper();
+
+ return [
+ columnHelper.accessor((row) => (row.type === "data" ? row.booking.eventType.id : null), {
+ id: "eventTypeId",
+ header: t("event_type"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.MULTI_SELECT,
+ },
+ },
+ }),
+ columnHelper.accessor((row) => (row.type === "data" ? row.booking.eventType.team?.id ?? null : null), {
+ id: "teamId",
+ header: t("team"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.MULTI_SELECT,
+ },
+ },
+ }),
+ columnHelper.accessor((row) => (row.type === "data" ? row.booking.user?.id ?? null : null), {
+ id: "userId",
+ header: t("member"),
+ enableColumnFilter: permissions.canReadOthersBookings,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.MULTI_SELECT,
+ },
+ },
+ }),
+ columnHelper.accessor((row) => row, {
+ id: "attendeeName",
+ header: t("attendee_name"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.TEXT,
+ },
+ },
+ }),
+ columnHelper.accessor((row) => row, {
+ id: "attendeeEmail",
+ header: t("attendee_email_variable"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.TEXT,
+ },
+ },
+ }),
+ columnHelper.accessor((row) => row, {
+ id: "dateRange",
+ header: t("date_range"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.DATE_RANGE,
+ dateRangeOptions: {
+ range: status === "past" ? "past" : "custom",
+ },
+ },
+ },
+ }),
+ columnHelper.accessor((row) => (row.type === "data" ? row.booking.uid : null), {
+ id: "bookingUid",
+ header: t("booking_uid"),
+ enableColumnFilter: true,
+ enableSorting: false,
+ cell: () => null,
+ meta: {
+ filter: {
+ type: ColumnFilterType.TEXT,
+ textOptions: {
+ allowedOperators: ["equals"],
+ },
+ },
+ },
+ }),
+ ];
+}
diff --git a/apps/web/modules/bookings/components/BookingDetailsSheet.tsx b/apps/web/modules/bookings/components/BookingDetailsSheet.tsx
index 4b7e347228..fa1f36961b 100644
--- a/apps/web/modules/bookings/components/BookingDetailsSheet.tsx
+++ b/apps/web/modules/bookings/components/BookingDetailsSheet.tsx
@@ -29,65 +29,91 @@ import {
import { BookingActionsDropdown } from "../../../components/booking/actions/BookingActionsDropdown";
import { BookingActionsStoreProvider } from "../../../components/booking/actions/BookingActionsStoreProvider";
import type { BookingListingStatus } from "../../../components/booking/types";
-import { buildBookingLink } from "../lib/buildBookingLink";
+import {
+ useBookingDetailsSheetStore,
+ useBookingDetailsSheetStoreApi,
+} from "../store/bookingDetailsSheetStore";
import type { BookingOutput } from "../types";
+import { JoinMeetingButton } from "./JoinMeetingButton";
type BookingMetaData = z.infer;
interface BookingDetailsSheetProps {
- booking: BookingOutput | null;
- isOpen: boolean;
- onClose: () => void;
userTimeZone?: string;
userTimeFormat?: number;
userId?: number;
userEmail?: string;
- onPrevious?: () => void;
- hasPrevious?: boolean;
- onNext?: () => void;
- hasNext?: boolean;
}
-interface BookingDetailsSheetInnerProps extends Omit {
- booking: BookingOutput;
-}
-
-export function BookingDetailsSheet(props: BookingDetailsSheetProps) {
- if (!props.booking) return null;
-
- return (
-
-
-
- );
-}
-
-function BookingDetailsSheetInner({
- booking,
- isOpen,
- onClose,
+export function BookingDetailsSheet({
+ userTimeZone,
+ userTimeFormat,
+ userId,
+ userEmail,
+}: BookingDetailsSheetProps) {
+ const booking = useBookingDetailsSheetStore((state) => state.getSelectedBooking());
+
+ // Return null if no booking is selected (sheet is closed)
+ if (!booking) return null;
+
+ return (
+
+
+
+ );
+}
+
+interface BookingDetailsSheetInnerProps {
+ booking: BookingOutput;
+ userTimeZone?: string;
+ userTimeFormat?: number;
+ userId?: number;
+ userEmail?: string;
+}
+
+function BookingDetailsSheetInner({
+ booking,
userTimeZone,
userTimeFormat,
userId,
userEmail,
- onPrevious,
- hasPrevious = false,
- onNext,
- hasNext = false,
}: BookingDetailsSheetInnerProps) {
const { t } = useLocale();
+ // Get navigation state directly from the store
+ const hasNext = useBookingDetailsSheetStore((state) => state.hasNext());
+ const hasPrevious = useBookingDetailsSheetStore((state) => state.hasPrevious());
+ const setSelectedBookingId = useBookingDetailsSheetStore((state) => state.setSelectedBookingId);
+
+ const handleClose = () => {
+ setSelectedBookingId(null);
+ };
+
+ const storeApi = useBookingDetailsSheetStoreApi();
+
+ const handleNext = () => {
+ const nextId = storeApi.getState().getNextBookingId();
+ if (nextId !== null) {
+ setSelectedBookingId(nextId);
+ }
+ };
+
+ const handlePrevious = () => {
+ const prevId = storeApi.getState().getPreviousBookingId();
+ if (prevId !== null) {
+ setSelectedBookingId(prevId);
+ }
+ };
+
const startTime = dayjs(booking.startTime).tz(userTimeZone);
const endTime = dayjs(booking.endTime).tz(userTimeZone);
- // Build booking confirmation link
- const isRecurring = booking.recurringEventId !== null;
- const bookingLink = buildBookingLink({
- bookingUid: booking.uid,
- allRemainingBookings: isRecurring,
- email: booking.attendees?.[0]?.email,
- });
-
const getStatusBadge = () => {
switch (booking.status) {
case "ACCEPTED":
@@ -108,14 +134,6 @@ function BookingDetailsSheetInner({
const parsedMetadata = bookingMetadataSchema.safeParse(booking.metadata ?? null);
const bookingMetadata = parsedMetadata.success ? parsedMetadata.data : null;
- // Get conference link info for Join button
- const { locationToDisplay, provider, isLocationURL } = useBookingLocation({
- location: booking.location,
- videoCallUrl: bookingMetadata?.videoCallUrl,
- t,
- bookingStatus: booking.status,
- });
-
const recurringInfo =
booking.recurringEventId && booking.eventType?.recurringEvent
? {
@@ -131,7 +149,7 @@ function BookingDetailsSheetInner({
: [];
return (
-
+
{
e.preventDefault();
- onPrevious?.();
+ handlePrevious();
}}
/>
@@ -183,6 +201,8 @@ function BookingDetailsSheetInner({
+