Files
calendar/packages/features/data-table/components/DataTableWrapper.tsx
T
Rodrigo EhlersGitHubEunjae LeeDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
89230474a5 feat: bookings page redesign v3 with calendar view (#24664)
* bookings page redesign work in progress

fix: duplicate translation key

chore: use newly supported separator type

remove outdated BookingDetailsSheet

remove dropdown and related code

revert unncessary changes

* fix wrong rebase

* fix type error

* refactor: separate bookings columns into filter and display columns (#24959)

* refactor: separate bookings columns into filter and display columns

- Extract filter-only columns into shared filterColumns.ts
- Extract list display columns into listColumns.tsx
- Create BookingsListContainer for list view with both column sets
- Create BookingsCalendarContainer for calendar view with filter columns only
- Refactor bookings-view.tsx to use dynamic imports for containers
- Remove column/table creation logic from bookings-view.tsx

This ensures calendar view doesn't import list-specific UI components (AvatarGroup, Badge, etc.)

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: return null instead of false for separator rows in filter accessors

The filter accessor functions were returning false for separator rows instead of null,
which would pollute the multi-select filters with bogus 'false' values.

This fix ensures that separator rows return null so they are properly excluded from filters.

Addresses cubic AI reviewer feedback on PR #24959

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* clean up filter column visibility

* feat: integrate booking calendar view with re-designed list (#24973)

* add toggle button

* remove the dateRange filter when switching from calendar to list view

* move "view" to the action dropdown

* add close button the details sheet

* move close button

* fix more button behavior

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix type error

* update the test case

* fix e2e util

* fix actions dropdown

* revert e2e tests

* fix type error on BookingActionsDropdown.tsx

* fix: include today's bookings in flatData for past status

Previously, flatData excluded groupedBookings.today, which caused
past bookings that happened today to not show up when status === 'past'.
This fix includes today's bookings in flatData for all statuses.

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* improve attendee cell

* fix e2e tests

* change max

* fix e2e

* add reschedule requested message

* fix e2e

* update e2e

* remove flaky checks

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-11-13 16:20:48 +00:00

166 lines
5.2 KiB
TypeScript

"use client";
import type { Row, VisibilityState } from "@tanstack/react-table";
import { noop } from "lodash";
import { useEffect, useRef } from "react";
import { useColumnFilters } from "../hooks/useColumnFilters";
import { useDataTable } from "../hooks/useDataTable";
import { useFetchMoreOnBottomReached } from "../hooks/useFetchMoreOnBottomReached";
import type { DataTablePropsFromWrapper } from "./DataTable";
import { DataTable } from "./DataTable";
import { DataTablePagination } from "./DataTablePagination";
type BaseDataTableWrapperProps<TData> = Omit<
DataTablePropsFromWrapper<TData>,
"paginationMode" | "tableContainerRef"
> & {
totalRowCount?: number;
ToolbarLeft?: React.ReactNode;
ToolbarRight?: React.ReactNode;
EmptyView?: React.ReactNode;
LoaderView?: React.ReactNode;
tableContainerRef?: React.RefObject<HTMLDivElement>;
onRowMouseclick?: (row: Row<TData>) => void;
rowTestId?: string | ((row: Row<TData>) => string | undefined);
rowDataAttributes?: (row: Row<TData>) => Record<string, string> | undefined;
};
type InfinitePaginationProps<TData> = BaseDataTableWrapperProps<TData> & {
paginationMode: "infinite";
hasNextPage: boolean;
fetchNextPage: () => void;
isFetching: boolean;
};
type StandardPaginationProps<TData> = BaseDataTableWrapperProps<TData> & {
paginationMode: "standard";
hasNextPage?: never;
fetchNextPage?: never;
isFetching?: never;
};
export type DataTableWrapperProps<TData> = InfinitePaginationProps<TData> | StandardPaginationProps<TData>;
export function DataTableWrapper<TData>({
testId,
bodyTestId,
table,
isPending,
hasNextPage,
fetchNextPage,
isFetching,
totalRowCount,
variant,
ToolbarLeft,
ToolbarRight,
EmptyView,
LoaderView,
className,
containerClassName,
headerClassName,
rowClassName,
rowTestId,
rowDataAttributes,
children,
tableContainerRef: externalRef,
paginationMode,
onRowMouseclick,
hideSeparatorsOnSort,
hideSeparatorsOnFilter,
separatorClassName,
}: DataTableWrapperProps<TData>) {
const internalRef = useRef<HTMLDivElement>(null);
const tableContainerRef = externalRef || internalRef;
const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({
tableContainerRef,
hasNextPage: paginationMode === "infinite" ? hasNextPage : false,
fetchNextPage: paginationMode === "infinite" ? fetchNextPage : noop,
isFetching: paginationMode === "infinite" ? isFetching : false,
enabled: paginationMode === "infinite",
});
const { sorting, setSorting, columnVisibility, setColumnVisibility } = useDataTable();
const columnFilters = useColumnFilters();
useEffect(() => {
const mergedColumnVisibility = {
...table.initialState?.columnVisibility,
...columnVisibility,
} satisfies VisibilityState;
table.setState((prev) => ({
...prev,
sorting,
columnFilters,
columnVisibility: mergedColumnVisibility,
}));
table.setOptions((prev) => ({
...prev,
onSortingChange: setSorting,
onColumnVisibilityChange: setColumnVisibility,
}));
}, [table, sorting, columnFilters, columnVisibility, setSorting, setColumnVisibility]);
let view: "loader" | "empty" | "table" = "table";
if (isPending && LoaderView) {
view = "loader";
} else if (table.getRowCount() === 0 && EmptyView) {
view = "empty";
}
return (
<>
{(ToolbarLeft || ToolbarRight || children) && (
<div className="grid w-full items-center gap-2 pb-4">
<div className="flex w-full flex-col gap-2">
<div className="flex w-full flex-wrap justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">{ToolbarLeft}</div>
<div className="flex flex-wrap items-center gap-2">{ToolbarRight}</div>
</div>
</div>
{children}
</div>
)}
{view === "loader" && LoaderView}
{view === "empty" && EmptyView}
{view === "table" && (
<DataTable
testId={testId}
bodyTestId={bodyTestId}
table={table}
tableContainerRef={tableContainerRef}
isPending={isPending}
enableColumnResizing={true}
variant={variant}
className={className}
containerClassName={containerClassName}
headerClassName={headerClassName}
rowClassName={rowClassName}
rowTestId={rowTestId}
rowDataAttributes={rowDataAttributes}
paginationMode={paginationMode}
onRowMouseclick={onRowMouseclick}
hasWrapperContext={true}
hideSeparatorsOnSort={hideSeparatorsOnSort}
hideSeparatorsOnFilter={hideSeparatorsOnFilter}
separatorClassName={separatorClassName}
onScroll={
paginationMode === "infinite"
? (e: Pick<React.UIEvent<HTMLDivElement, UIEvent>, "target">) =>
fetchMoreOnBottomReached(e.target as HTMLDivElement)
: undefined
}>
<div style={{ gridArea: "footer" }} className="px-3 py-2">
<DataTablePagination<TData>
table={table}
totalRowCount={totalRowCount}
paginationMode={paginationMode}
/>
</div>
</DataTable>
)}
</>
);
}