From 86353191d2545f49d206bbfc1b8f2c256a200326 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 11 Nov 2025 17:14:48 +0100 Subject: [PATCH] feat: add booking actions into BookingDetailsSheet (#24912) * move booking actions into a subfolder * fix logic to render booking actions dropdown * skip dropdown portal when rendered from detail sheet * fix type error --- .../components/booking/BookingListItem.tsx | 11 +- .../{ => actions}/BookingActionsDropdown.tsx | 200 ++++++++++-------- .../BookingActionsStoreProvider.tsx | 0 .../{ => actions}/bookingActions.test.ts | 2 +- .../booking/{ => actions}/bookingActions.ts | 2 +- .../components/booking/{ => actions}/store.ts | 0 apps/web/components/booking/types.ts | 2 +- .../components/BookingDetailsSheet.tsx | 44 +++- .../modules/bookings/views/bookings-view.tsx | 2 + apps/web/public/static/locales/en/common.json | 1 + 10 files changed, 165 insertions(+), 99 deletions(-) rename apps/web/components/booking/{ => actions}/BookingActionsDropdown.tsx (79%) rename apps/web/components/booking/{ => actions}/BookingActionsStoreProvider.tsx (100%) rename apps/web/components/booking/{ => actions}/bookingActions.test.ts (99%) rename apps/web/components/booking/{ => actions}/bookingActions.ts (99%) rename apps/web/components/booking/{ => actions}/store.ts (100%) diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index cf9b94e2c0..c869c1d3ba 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -46,8 +46,11 @@ import { Tooltip } from "@calcom/ui/components/tooltip"; import assignmentReasonBadgeTitleMap from "@lib/booking/assignmentReasonBadgeTitleMap"; import { buildBookingLink } from "../../modules/bookings/lib/buildBookingLink"; -import { BookingActionsDropdown } from "./BookingActionsDropdown"; -import { useBookingActionsStoreContext, BookingActionsStoreProvider } from "./BookingActionsStoreProvider"; +import { BookingActionsDropdown } from "./actions/BookingActionsDropdown"; +import { + useBookingActionsStoreContext, + BookingActionsStoreProvider, +} from "./actions/BookingActionsStoreProvider"; import { getPendingActions, getCancelEventAction, @@ -56,7 +59,7 @@ import { shouldShowIndividualReportButton, type BookingActionContext, getReportAction, -} from "./bookingActions"; +} from "./actions/bookingActions"; import type { BookingItemProps } from "./types"; type ParsedBooking = ReturnType; @@ -496,7 +499,7 @@ function BookingListItem(booking: BookingItemProps) {
{shouldShowPendingActions(actionContext) && } - + {shouldShowRecurringCancelAction(actionContext) && } {shouldShowIndividualReportButton(actionContext) && (
diff --git a/apps/web/components/booking/BookingActionsDropdown.tsx b/apps/web/components/booking/actions/BookingActionsDropdown.tsx similarity index 79% rename from apps/web/components/booking/BookingActionsDropdown.tsx rename to apps/web/components/booking/actions/BookingActionsDropdown.tsx index 9b96c573de..13c04b8915 100644 --- a/apps/web/components/booking/BookingActionsDropdown.tsx +++ b/apps/web/components/booking/actions/BookingActionsDropdown.tsx @@ -31,6 +31,7 @@ import { ReportBookingDialog } from "@components/dialog/ReportBookingDialog"; import { RerouteDialog } from "@components/dialog/RerouteDialog"; import { RescheduleDialog } from "@components/dialog/RescheduleDialog"; +import type { BookingItemProps } from "../types"; import { useBookingActionsStoreContext } from "./BookingActionsStoreProvider"; import { getCancelEventAction, @@ -40,13 +41,14 @@ import { shouldShowEditActions, type BookingActionContext, } from "./bookingActions"; -import type { BookingItemProps } from "./types"; interface BookingActionsDropdownProps { booking: BookingItemProps; + context: "booking-list-item" | "booking-details-sheet"; + size?: "xs" | "sm" | "base" | "lg"; } -export function BookingActionsDropdown({ booking }: BookingActionsDropdownProps) { +export function BookingActionsDropdown({ booking, context, size = "base" }: BookingActionsDropdownProps) { const { t } = useLocale(); const utils = trpc.useUtils(); @@ -236,9 +238,11 @@ export function BookingActionsDropdown({ booking }: BookingActionsDropdownProps) const cancelEventAction = getCancelEventAction(actionContext); + const shouldShowEdit = shouldShowEditActions(actionContext); const baseEditEventActions = getEditEventActions(actionContext); const editEventActions: ActionType[] = baseEditEventActions.map((action) => ({ ...action, + disabled: !shouldShowEdit || action.disabled, // Disable all edit actions if shouldn't show edit actions onClick: action.id === "reschedule_request" ? () => setIsOpenRescheduleDialog(true) @@ -494,99 +498,119 @@ export function BookingActionsDropdown({ booking }: BookingActionsDropdownProps) ); - // Don't render dropdown if edit actions shouldn't be shown - if (!shouldShowEditActions(actionContext)) { + // Check if there are any available actions across all action groups + const hasAnyAvailableActions = () => { + // 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); + + // 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 hasAvailableEditAction || hasAvailableAfterAction || isReportAvailable || isCancelAvailable; + }; + + // Don't render dropdown if no actions are available + if (!hasAnyAvailableActions()) { return dialogs; } + const menuContent = ( + + {t("edit_event")} + {editEventActions.map((action) => ( + + + {action.label} + + + ))} + + {t("after_event")} + {afterEventActions.map((action) => ( + + + {action.label} + + + ))} + <> + + + + {reportActionWithHandler.label} + + + + + + + {cancelEventAction.label} + + + + ); + return ( <> {dialogs} - - - +
diff --git a/apps/web/modules/bookings/views/bookings-view.tsx b/apps/web/modules/bookings/views/bookings-view.tsx index dcc1c8f171..48678a0f79 100644 --- a/apps/web/modules/bookings/views/bookings-view.tsx +++ b/apps/web/modules/bookings/views/bookings-view.tsx @@ -473,6 +473,8 @@ function BookingsContent({ status, permissions, isCalendarViewEnabled }: Booking onClose={() => setSelectedBookingId(null)} userTimeZone={user?.timeZone} userTimeFormat={user?.timeFormat === null ? undefined : user?.timeFormat} + userId={user?.id} + userEmail={user?.email} onPrevious={bookingNavigation.onPrevious} hasPrevious={bookingNavigation.hasPrevious} onNext={bookingNavigation.onNext} diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 91aa0c2ffd..6940009ca7 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3968,5 +3968,6 @@ "team_invite_subtitle": "Invite team members to collaborate and schedule together", "team_onboarding_details_subtitle": "Add your team's name and create a unique URL for your team", "view_booking_details": "View Booking Details", + "view": "View", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" }