-
+
+
-
- {/* Time column - 140px */}
-
-
+
-
- {/* Event column - 200px */}
-
-
-
-
- {/* Who column - 160px, Avatar group */}
-
- {isHeader && }
- {!isHeader && (
- <>
-
-
-
- >
- )}
-
-
- {/* Team column - 140px */}
-
-
-
-
- {/* Actions column - 280px */}
-
- {isHeader && }
- {!isHeader && (
- <>
-
-
-
- >
- )}
-
-
+
);
}
diff --git a/apps/web/components/booking/actions/BookingActionsDropdown.tsx b/apps/web/components/booking/actions/BookingActionsDropdown.tsx
index 66cc5b5ea7..13c04b8915 100644
--- a/apps/web/components/booking/actions/BookingActionsDropdown.tsx
+++ b/apps/web/components/booking/actions/BookingActionsDropdown.tsx
@@ -15,9 +15,9 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
- DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuTrigger,
+ DropdownMenuPortal,
} from "@calcom/ui/components/dropdown";
import { TextAreaField } from "@calcom/ui/components/form";
import type { ActionType } from "@calcom/ui/components/table";
@@ -31,7 +31,6 @@ import { ReportBookingDialog } from "@components/dialog/ReportBookingDialog";
import { RerouteDialog } from "@components/dialog/RerouteDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
-import { buildBookingLink } from "../../../modules/bookings/lib/buildBookingLink";
import type { BookingItemProps } from "../types";
import { useBookingActionsStoreContext } from "./BookingActionsStoreProvider";
import {
@@ -40,29 +39,16 @@ import {
getAfterEventActions,
getReportAction,
shouldShowEditActions,
- shouldShowPendingActions,
- getPendingActions,
type BookingActionContext,
} from "./bookingActions";
interface BookingActionsDropdownProps {
booking: BookingItemProps;
+ context: "booking-list-item" | "booking-details-sheet";
size?: "xs" | "sm" | "base" | "lg";
- className?: string;
- /**
- * Whether to use a portal for the dropdown menu.
- * Set to false when rendering inside a Sheet/Dialog to keep the dropdown within the modal's stacking context.
- * @default true
- */
- usePortal?: boolean;
}
-export function BookingActionsDropdown({
- booking,
- size = "base",
- className,
- usePortal = true,
-}: BookingActionsDropdownProps) {
+export function BookingActionsDropdown({ booking, context, size = "base" }: BookingActionsDropdownProps) {
const { t } = useLocale();
const utils = trpc.useUtils();
@@ -178,13 +164,6 @@ export function BookingActionsDropdown({
const isBookingFromRoutingForm = !!booking.routedFromRoutingFormReponse && !!booking.eventType?.team;
- // Build booking confirmation link
- const bookingLink = buildBookingLink({
- bookingUid: booking.uid,
- allRemainingBookings: isRecurring,
- email: booking.attendees?.[0]?.email,
- });
-
const userEmail = booking.loggedInUser.userEmail;
const userSeat = booking.seatsReferences.find((seat) => !!userEmail && seat.attendee?.email === userEmail);
const isAttendee = !!userSeat;
@@ -232,10 +211,6 @@ export function BookingActionsDropdown({
}
};
- // Calculate showPendingPayment based on payment logic
- const hasPayment = booking.payment.length > 0;
- const showPendingPayment = hasPayment;
-
const actionContext: BookingActionContext = {
booking,
isUpcoming,
@@ -253,7 +228,7 @@ export function BookingActionsDropdown({
isDisabledCancelling,
isDisabledRescheduling,
isCalVideoLocation,
- showPendingPayment,
+ showPendingPayment: false, // This will be calculated below
isAttendee,
cardCharged,
attendeeList,
@@ -263,20 +238,6 @@ export function BookingActionsDropdown({
const cancelEventAction = getCancelEventAction(actionContext);
- // Get pending actions (accept/reject)
- const shouldShowPending = shouldShowPendingActions(actionContext);
- const basePendingActions = shouldShowPending ? getPendingActions(actionContext) : [];
- const pendingActions: ActionType[] = basePendingActions.map((action) => ({
- ...action,
- disabled: mutation.isPending,
- onClick:
- action.id === "confirm"
- ? () => bookingConfirm(true)
- : action.id === "reject"
- ? () => setRejectionDialogIsOpen(true)
- : undefined,
- })) as ActionType[];
-
const shouldShowEdit = shouldShowEditActions(actionContext);
const baseEditEventActions = getEditEventActions(actionContext);
const editEventActions: ActionType[] = baseEditEventActions.map((action) => ({
@@ -539,26 +500,22 @@ export function BookingActionsDropdown({
// Check if there are any available actions across all action groups
const hasAnyAvailableActions = () => {
- // Check if any pending action is available
- const hasAvailablePendingAction = pendingActions.some((action) => !action.disabled);
-
// Check if any edit action is available
const hasAvailableEditAction = editEventActions.some((action) => !action.disabled);
// Check if any after event action is available
const hasAvailableAfterAction = afterEventActions.some((action) => !action.disabled);
- // Check report and cancel actions
+ // For booking-list-item context, only check edit and after event actions
+ if (context === "booking-list-item") {
+ return hasAvailableEditAction || hasAvailableAfterAction;
+ }
+
+ // For booking-details-sheet context, also check report and cancel actions
const isReportAvailable = !reportActionWithHandler.disabled;
const isCancelAvailable = !cancelEventAction.disabled;
- return (
- hasAvailablePendingAction ||
- hasAvailableEditAction ||
- hasAvailableAfterAction ||
- isReportAvailable ||
- isCancelAvailable
- );
+ return hasAvailableEditAction || hasAvailableAfterAction || isReportAvailable || isCancelAvailable;
};
// Don't render dropdown if no actions are available
@@ -566,150 +523,94 @@ export function BookingActionsDropdown({
return dialogs;
}
- // Conditional portal wrapper to avoid portal when inside Sheet/Dialog
- const ConditionalPortal = usePortal
- ? DropdownMenuPortal
- : ({ children }: { children: React.ReactNode }) => <>{children}>;
+ const menuContent = (
+
+ {t("edit_event")}
+ {editEventActions.map((action) => (
+
+
+ {action.label}
+
+
+ ))}
+
+ {t("after_event")}
+ {afterEventActions.map((action) => (
+
+
+ {action.label}
+
+
+ ))}
+ <>
+
+
+
+ {reportActionWithHandler.label}
+
+
+ >
+
+
+
+ {cancelEventAction.label}
+
+
+
+ );
return (
<>
{dialogs}
-
+
-
-
-
-
- e.stopPropagation()}>
- {t("view")}
-
-
-
- {pendingActions.length > 0 && (
- <>
- {t("booking_response")}
- {pendingActions.map((action) => (
-
- {
- e.stopPropagation();
- action.onClick?.(e);
- }}
- data-bookingid={action.bookingId}
- data-testid={action.id}
- className={action.disabled ? "text-muted" : undefined}>
- {action.label}
-
-
- ))}
-
- >
- )}
- {t("edit_event")}
- {editEventActions.map((action) => (
-
- {
- e.stopPropagation();
- action.onClick?.(e);
- }}
- data-bookingid={action.bookingId}
- data-testid={action.id}
- className={action.disabled ? "text-muted" : undefined}>
- {action.label}
-
-
- ))}
-
- {t("after_event")}
- {afterEventActions.map((action) => (
-
- {
- e.stopPropagation();
- action.onClick?.(e);
- }}
- disabled={action.disabled}
- data-bookingid={action.bookingId}
- data-testid={action.id}
- className={action.disabled ? "text-muted" : undefined}>
- {action.label}
-
-
- ))}
- <>
-
-
- {
- e.stopPropagation();
- reportActionWithHandler.onClick?.();
- }}
- disabled={reportActionWithHandler.disabled}
- data-testid={reportActionWithHandler.id}
- className={reportActionWithHandler.disabled ? "text-muted" : undefined}>
- {reportActionWithHandler.label}
-
-
- >
-
-
- {
- e.stopPropagation();
- cancelEventAction.onClick?.(e);
- }}
- disabled={cancelEventAction.disabled}
- data-bookingid={cancelEventAction.bookingId}
- data-testid={cancelEventAction.id}
- className={cancelEventAction.disabled ? "text-muted" : undefined}>
- {cancelEventAction.label}
-
-
-
-
+ {context === "booking-details-sheet" ? (
+ menuContent
+ ) : (
+ {menuContent}
+ )}
>
);
diff --git a/apps/web/components/booking/actions/bookingActions.test.ts b/apps/web/components/booking/actions/bookingActions.test.ts
index ec17b18531..586add5f09 100644
--- a/apps/web/components/booking/actions/bookingActions.test.ts
+++ b/apps/web/components/booking/actions/bookingActions.test.ts
@@ -137,18 +137,18 @@ describe("Booking Actions", () => {
expect(actions).toHaveLength(2);
expect(actions[0]).toEqual({
+ id: "reject",
+ label: "reject",
+ icon: "ban",
+ disabled: false,
+ });
+ expect(actions[1]).toEqual({
id: "confirm",
bookingId: 1,
label: "confirm",
icon: "check",
disabled: false,
});
- expect(actions[1]).toEqual({
- id: "reject",
- label: "reject",
- icon: "ban",
- disabled: false,
- });
});
it("should return reject action only for non-pending booking", () => {
@@ -167,8 +167,8 @@ describe("Booking Actions", () => {
});
const actions = getPendingActions(context);
- expect(actions[0].label).toBe("confirm_all");
- expect(actions[1].label).toBe("reject_all");
+ expect(actions[0].label).toBe("reject_all");
+ expect(actions[1].label).toBe("confirm_all");
});
});
diff --git a/apps/web/components/booking/actions/bookingActions.ts b/apps/web/components/booking/actions/bookingActions.ts
index 4c3de355a7..da09095a89 100644
--- a/apps/web/components/booking/actions/bookingActions.ts
+++ b/apps/web/components/booking/actions/bookingActions.ts
@@ -38,7 +38,14 @@ export function getPendingActions(context: BookingActionContext): ActionType[] {
const { booking, isPending, isTabRecurring, isTabUnconfirmed, isRecurring, showPendingPayment, t } =
context;
- const actions: ActionType[] = [];
+ const actions: ActionType[] = [
+ {
+ id: "reject",
+ label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("reject_all") : t("reject"),
+ icon: "ban",
+ disabled: false, // This would be controlled by mutation state in the component
+ },
+ ];
// For bookings with payment, only confirm if the booking is paid for
// Original logic: (isPending && !paymentAppData.enabled) || (paymentAppData.enabled && !!paymentAppData.price && booking.paid)
@@ -52,13 +59,6 @@ export function getPendingActions(context: BookingActionContext): ActionType[] {
});
}
- actions.push({
- id: "reject",
- label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("reject_all") : t("reject"),
- icon: "ban",
- disabled: false, // This would be controlled by mutation state in the component
- });
-
return actions;
}
diff --git a/apps/web/components/dialog/RescheduleDialog.tsx b/apps/web/components/dialog/RescheduleDialog.tsx
index 94171b8773..94dc20bb94 100644
--- a/apps/web/components/dialog/RescheduleDialog.tsx
+++ b/apps/web/components/dialog/RescheduleDialog.tsx
@@ -36,7 +36,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
return (