* fix * move event types components to web * update import paths * mv apps components * migrate form builder * fix * mv sso * fix * mv * update import paths * update import paths * mv * mv * mv * fix * update Booker * fix * fix * fix * fix * mv video * mv embed components to web * update import paths * mv calendar weekly view components * update import paths * fix * fixp * fix * fix * fix * fix: update FormBuilder imports to use @calcom/features/form-builder Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update broken import paths after file migrations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: correct import paths for platform atoms and moved components Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: apply CSS type fixes and add missing atoms exports Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: resolve type errors in test files after component migrations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: resolve remaining type errors in test files Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix * migrate * fix: resolve type errors in test and mock files - Add missing bookingForm, bookerFormErrorRef, instantConnectCooldownMs to Booker.test.tsx bookings prop - Add all required BookerEvent properties to event.mock.ts - Add vi import from vitest to all mock files - Fix date parameter types in packages/dayjs/__mocks__/index.ts - Add verificationCode and setVerificationCode to test-utils.tsx mock store - Remove children.type access in Section.tsx mock to fix type error - Fix lint issues: remove unused React imports, use import type where needed, add return types - Add biome-ignore comments for pre-existing lint warnings in test files Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * migrate * migrate * migrate * update import paths * update import paths * update import paths * fix * migrate data table * migrate data table * fix * fix * fix * migrate insights components * migrate insights components * fix * mv * update import paths * fix * fix * fix * fix * fix * fix: resolve type errors in test mocks - Booker.test.tsx: Add all required UseFormReturn methods to bookingForm mock - event.mock.ts: Fix entity, subsetOfHosts, instantMeetingParameters, fieldTranslations, image types - dayjs/__mocks__/index.ts: Use Object.assign for proper typing of mock properties - Section.tsx: Change 'class' to 'className' in JSX with biome-ignore comment Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing hasDataErrors and dataErrors to bookings.errors mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing loadingStates properties to bookings mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing slots properties (setTentativeSelectedTimeslots, tentativeSelectedTimeslots, slotReservationId) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update quickAvailabilityChecks to include utcEndIso and use valid status type Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing bookerForm properties (formName, beforeVerifyEmail, formErrors) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing UseFormReturn properties to bookerForm.bookingForm mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add hasFormErrors and formErrors to bookerForm.formErrors mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add hasFormErrors and formErrors to bookerForm.errors mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing isError property to mockEvent Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: use complete BookerEvent mock in Booker.test.tsx Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: use branded bookingFields type in event mock Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing schedule mock properties (isError, isSuccess, isLoading, dataUpdatedAt) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * revert * fix * fix * fix * fix build * fix * fix * fix * fix: correct AddMembersWithSwitch test wrapper to use initial assignAllTeamMembers value - Fixed test wrapper to initialize useState with componentProps.assignAllTeamMembers instead of hardcoded false, allowing tests to properly test different states - Updated test expectations for ALL_TEAM_MEMBERS_ENABLED_AND_SEGMENT_NOT_APPLICABLE state to match actual component behavior (toggle should be present and checked) - Fixed 'should show Segment when toggled on' test to start with assignAllTeamMembers: false to properly test the flow of enabling it - Added explicit types to satisfy biome lint requirements Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: use JSX.Element instead of React.JSX.Element for type compatibility Co-Authored-By: benny@cal.com <sldisek783@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
209 lines
6.3 KiB
TypeScript
209 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import type { Row, VisibilityState } from "@tanstack/react-table";
|
|
import { noop } from "lodash";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
import { useColumnFilters } from "@calcom/features/data-table/hooks/useColumnFilters";
|
|
import { useDataTable } from "@calcom/features/data-table/hooks/useDataTable";
|
|
import { useFetchMoreOnBottomReached } from "@calcom/features/data-table/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;
|
|
ErrorView?: React.ReactNode;
|
|
hasError?: boolean;
|
|
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,
|
|
ErrorView,
|
|
hasError,
|
|
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]);
|
|
|
|
// Scroll to top when table data changes
|
|
useScrollToTopOnDataChange({
|
|
tableData: table.options.data,
|
|
paginationMode,
|
|
});
|
|
|
|
let view: "loader" | "empty" | "error" | "table" = "table";
|
|
if (hasError && ErrorView) {
|
|
view = "error";
|
|
} else 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 === "error" && ErrorView}
|
|
{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>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Scrolls to top when table data changes (pagination, filters, etc.)
|
|
* Only active for standard pagination mode where we show discrete pages
|
|
*/
|
|
function useScrollToTopOnDataChange<TData>({
|
|
tableData,
|
|
paginationMode,
|
|
}: {
|
|
tableData: TData[];
|
|
paginationMode: "standard" | "infinite";
|
|
}) {
|
|
const previousDataRef = useRef(tableData);
|
|
const isInitialMount = useRef(true);
|
|
|
|
useEffect(() => {
|
|
// Skip on initial mount
|
|
if (isInitialMount.current) {
|
|
isInitialMount.current = false;
|
|
return;
|
|
}
|
|
|
|
const hasDataChanged = previousDataRef.current !== tableData;
|
|
previousDataRef.current = tableData;
|
|
|
|
if (paginationMode === "standard" && hasDataChanged) {
|
|
window.scrollTo(0, 0);
|
|
}
|
|
}, [tableData, paginationMode]);
|
|
}
|