"use client"; import dayjs from "@calcom/dayjs"; import { useDataTable } from "~/data-table/hooks/useDataTable"; import { useDisplayedFilterCount } from "~/data-table/hooks/useDisplayedFilterCount"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { Alert } from "@calcom/ui/components/alert"; import { Badge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { ToggleGroup } from "@calcom/ui/components/form"; import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton"; import { getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useBookingFilters } from "~/bookings/hooks/useBookingFilters"; import { useBookingListColumns } from "~/bookings/hooks/useBookingListColumns"; import { useBookingListData } from "~/bookings/hooks/useBookingListData"; import { useBookingStatusTab } from "~/bookings/hooks/useBookingStatusTab"; import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues"; import { useListAutoSelector } from "~/bookings/hooks/useListAutoSelector"; import { useSwitchToCorrectStatusTab } from "~/bookings/hooks/useSwitchToCorrectStatusTab"; import { DataTableFilters, DataTableSegment } from "~/data-table/components"; import { BookingDetailsSheetStoreProvider, useBookingDetailsSheetStore, } from "../store/bookingDetailsSheetStore"; import type { BookingListingStatus, BookingsGetOutput, RowData } from "../types"; import { BookingDetailsSheet } from "./BookingDetailsSheet"; import { BookingList } from "./BookingList"; import { ViewToggleButton } from "./ViewToggleButton"; interface FilterButtonProps { table: ReturnType>; displayedFilterCount: number; setShowFilters: (value: boolean | ((prev: boolean) => boolean)) => void; } function FilterButton({ table, displayedFilterCount, setShowFilters }: FilterButtonProps) { const { t } = useLocale(); if (displayedFilterCount === 0) { return ; } return ( ); } interface BookingListContainerProps { status: BookingListingStatus; permissions: { canReadOthersBookings: boolean; }; bookingsV3Enabled: boolean; bookingAuditEnabled: boolean; } interface BookingListInnerProps extends BookingListContainerProps { data?: BookingsGetOutput; isPending: boolean; hasError: boolean; errorMessage?: string; totalRowCount?: number; bookings: BookingsGetOutput["bookings"]; } function BookingListInner({ status, permissions, bookings, bookingsV3Enabled, bookingAuditEnabled, data, isPending, hasError, errorMessage, totalRowCount, }: BookingListInnerProps) { const { t } = useLocale(); const user = useMeQuery().data; const setSelectedBookingUid = useBookingDetailsSheetStore((state) => state.setSelectedBookingUid); const router = useRouter(); const [showFilters, setShowFilters] = useState(true); // Handle auto-selection for list view useListAutoSelector(bookings); const ErrorView = errorMessage ? ( ) : undefined; const handleBookingClick = useCallback( (bookingUid: string) => { setSelectedBookingUid(bookingUid); }, [setSelectedBookingUid] ); const columns = useBookingListColumns({ user, status, canReadOthersBookings: permissions.canReadOthersBookings, bookingsV3Enabled, handleBookingClick, }); const finalData = useBookingListData({ data, status, userTimeZone: user?.timeZone, }); const getFacetedUniqueValues = useFacetedUniqueValues({ canReadOthersBookings: permissions.canReadOthersBookings, }); const displayedFilterCount = useDisplayedFilterCount(); const { currentTab, tabOptions } = useBookingStatusTab(); useEffect(() => { if (displayedFilterCount === 0) { // reset to true, so it shows filters as soon as any filter is applied setShowFilters(true); } }, [displayedFilterCount]); const table = useReactTable({ data: finalData, columns, initialState: { columnVisibility: { eventTypeId: false, teamId: false, userId: false, attendeeName: false, attendeeEmail: false, dateRange: false, bookingUid: false, }, }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedUniqueValues, }); const isEmpty = !data?.bookings || data.bookings.length === 0; return ( <>
{/* Desktop: full width on first row, Mobile: full width on first row with horizontal scroll */}
{ if (!value) return; const selectedTab = tabOptions.find((tab) => tab.value === value); if (selectedTab?.href) { router.push(selectedTab.href); } }} options={tabOptions} />
{/* Desktop: second item on first row, Mobile: first item on second row */} {/* Desktop: auto-pushed to right via flex-grow spacer, Mobile: continue on second row */}
{/* */} {bookingsV3Enabled && }
{displayedFilterCount > 0 && showFilters && (
{/* Desktop: auto-pushed to right via flex-grow spacer */}
)} {status === "upcoming" && !isEmpty && ( )}
{bookingsV3Enabled && ( )} ); } export function BookingListContainer(props: BookingListContainerProps) { const { limit, offset, isValidatorPending } = useDataTable(); const { eventTypeIds, teamIds, userIds, dateRange, attendeeName, attendeeEmail, bookingUid } = useBookingFilters(); const { resolvedTabStatus, isResolvingTabStatus, preSelectedBooking } = useSwitchToCorrectStatusTab({ defaultStatus: props.status, }); // Build query input once - shared between query and prefetching const queryInput = useMemo( () => ({ limit, offset, filters: { statuses: [resolvedTabStatus], eventTypeIds, teamIds, userIds, attendeeName, attendeeEmail, bookingUid, afterStartDate: dateRange?.startDate ? dayjs(dateRange?.startDate).startOf("day").toISOString() : undefined, beforeEndDate: dateRange?.endDate ? dayjs(dateRange?.endDate).endOf("day").toISOString() : undefined, }, }), [ limit, offset, resolvedTabStatus, eventTypeIds, teamIds, userIds, attendeeName, attendeeEmail, bookingUid, dateRange, ] ); const query = trpc.viewer.bookings.get.useQuery(queryInput, { staleTime: 5 * 60 * 1000, // 5 minutes - data is considered fresh gcTime: 30 * 60 * 1000, // 30 minutes - cache retention time // We wait for tab status to be resolved before fetching, so that we can fetch the correct bookings as per resolved tab status enabled: !isValidatorPending && !isResolvingTabStatus, // Wait for validator to be ready before fetching }); const bookings = useMemo(() => { const queryBookings = query.data?.bookings ?? []; if (!preSelectedBooking) return queryBookings; if (queryBookings.some((b) => b.uid === preSelectedBooking.uid)) return queryBookings; // It ensures that the drawer opens for a booking that isn't even in the bookings list // Note that, bookings list doesn't use this so, it won't be visible in the list view but drawer will open for it // We don't want to show this booking in the list view, as it might not match the filters/pagination applied return [...queryBookings, preSelectedBooking]; }, [query.data?.bookings, preSelectedBooking]); // Always call the hook and provide navigation capabilities // The BookingDetailsSheet is only rendered when bookingsV3Enabled is true (see line 212) // const capabilities = useListNavigationCapabilities({ // limit, // offset, // totalCount: query.data?.totalCount, // setPageIndex, // queryInput, // }); return ( ); }