fix: separate Filter Segment implementation from DataTableProvider (#20587)

* fix: separate Filter Segment implementation from DataTableProvider

* disable components

* early return

* fix type error

* add missing export
This commit is contained in:
Eunjae Lee
2025-04-09 09:53:02 +00:00
committed by GitHub
parent 70eff63529
commit 5935cf7f1b
14 changed files with 123 additions and 68 deletions
@@ -17,6 +17,7 @@ import {
ZDateRangeFilterValue,
ZTextFilterValue,
} from "@calcom/features/data-table";
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
@@ -86,7 +87,7 @@ type BookingsProps = {
export default function Bookings(props: BookingsProps) {
return (
<DataTableProvider>
<DataTableProvider useSegments={useSegments}>
<BookingsContent {...props} />
</DataTableProvider>
);
@@ -1,6 +1,7 @@
"use client";
import { DataTableProvider } from "@calcom/features/data-table/DataTableProvider";
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
import {
RoutingFormResponsesTable,
FailedBookingsByField,
@@ -13,7 +14,7 @@ export default function InsightsRoutingFormResponsesPage() {
const { t } = useLocale();
return (
<DataTableProvider>
<DataTableProvider useSegments={useSegments}>
<InsightsOrgTeamsProvider>
<div className="mb-4 space-y-4">
<RoutingFormResponsesTable />
@@ -7,7 +7,7 @@ import { usePathname } from "next/navigation";
import { useQueryState, parseAsArrayOf, parseAsJson, parseAsInteger, parseAsString } from "nuqs";
import { createContext, useCallback, useEffect, useRef, useMemo } from "react";
import { useSegments } from "./lib/segments";
import { useSegmentsNoop } from "./hooks/useSegmentsNoop";
import {
type FilterValue,
ZSorting,
@@ -16,6 +16,7 @@ import {
ZColumnSizing,
type FilterSegmentOutput,
type ActiveFilters,
type UseSegments,
} from "./lib/types";
import { CTA_CONTAINER_CLASS_NAME } from "./lib/utils";
@@ -51,6 +52,7 @@ export type DataTableContextType = {
segmentId: number | undefined;
setSegmentId: (id: number | null) => void;
canSaveSegment: boolean;
isSegmentEnabled: boolean;
searchTerm: string;
setSearchTerm: (searchTerm: string | null) => void;
@@ -65,6 +67,7 @@ const DEFAULT_COLUMN_SIZING: ColumnSizingState = {};
const DEFAULT_PAGE_SIZE = 10;
interface DataTableProviderProps {
useSegments?: UseSegments;
tableIdentifier?: string;
children: React.ReactNode;
ctaContainerClassName?: string;
@@ -74,6 +77,7 @@ interface DataTableProviderProps {
export function DataTableProvider({
tableIdentifier: _tableIdentifier,
children,
useSegments = useSegmentsNoop,
defaultPageSize = DEFAULT_PAGE_SIZE,
ctaContainerClassName = CTA_CONTAINER_CLASS_NAME,
}: DataTableProviderProps) {
@@ -165,36 +169,38 @@ export function DataTableProvider({
[setPageSize, setPageIndex, defaultPageSize]
);
const { segments, selectedSegment, canSaveSegment, setSegmentIdAndSaveToLocalStorage } = useSegments({
tableIdentifier,
activeFilters,
sorting,
columnVisibility,
columnSizing,
pageSize,
searchTerm,
defaultPageSize,
segmentId,
setSegmentId,
setActiveFilters,
setSorting,
setColumnVisibility,
setColumnSizing,
setPageSize,
setPageIndex,
setSearchTerm,
});
const { segments, selectedSegment, canSaveSegment, setAndPersistSegmentId, isSegmentEnabled } = useSegments(
{
tableIdentifier,
activeFilters,
sorting,
columnVisibility,
columnSizing,
pageSize,
searchTerm,
defaultPageSize,
segmentId,
setSegmentId,
setActiveFilters,
setSorting,
setColumnVisibility,
setColumnSizing,
setPageSize,
setPageIndex,
setSearchTerm,
}
);
const clearAll = useCallback(
(exclude?: string[]) => {
setSegmentIdAndSaveToLocalStorage(null);
setAndPersistSegmentId(null);
setPageIndex(null);
setActiveFilters((prev) => {
const remainingFilters = prev.filter((filter) => exclude?.includes(filter.f));
return remainingFilters.length === 0 ? null : remainingFilters;
});
},
[setActiveFilters, setPageIndex, setSegmentIdAndSaveToLocalStorage]
[setActiveFilters, setPageIndex, setAndPersistSegmentId]
);
const ctaContainerRef = useRef<HTMLDivElement | null>(null);
@@ -230,8 +236,9 @@ export function DataTableProvider({
segments,
selectedSegment,
segmentId: segmentId || undefined,
setSegmentId: setSegmentIdAndSaveToLocalStorage,
setSegmentId: setAndPersistSegmentId,
canSaveSegment,
isSegmentEnabled,
searchTerm,
setSearchTerm: setDebouncedSearchTerm,
}}>
@@ -23,7 +23,7 @@ import {
} from "@calcom/ui/components/table";
import { useColumnSizingVars } from "../hooks";
import { usePersistentColumnResizing } from "../lib/resizing";
import { useColumnResizing } from "../hooks/useColumnResizing";
export type DataTablePropsFromWrapper<TData> = {
table: ReactTableType<TData>;
@@ -96,7 +96,7 @@ export function DataTable<TData>({
const columnSizingVars = useColumnSizingVars({ table });
usePersistentColumnResizing({
useColumnResizing({
enabled: Boolean(enableColumnResizing),
table,
tableContainerRef,
@@ -31,7 +31,7 @@ type SubmenuItem = {
export function FilterSegmentSelect() {
const { t } = useLocale();
const { segments, selectedSegment, segmentId, setSegmentId } = useDataTable();
const { segments, selectedSegment, segmentId, setSegmentId, isSegmentEnabled } = useDataTable();
const [segmentToRename, setSegmentToRename] = useState<FilterSegmentOutput | undefined>();
const [segmentToDuplicate, setSegmentToDuplicate] = useState<FilterSegmentOutput | undefined>();
const [segmentToDelete, setSegmentToDelete] = useState<FilterSegmentOutput | undefined>();
@@ -99,6 +99,14 @@ export function FilterSegmentSelect() {
];
}, [segments, t]);
if (!isSegmentEnabled) {
return (
<Button color="secondary" StartIcon="list-filter" EndIcon="chevron-down" disabled>
{t("segment")}
</Button>
);
}
return (
<>
<Dropdown>
@@ -50,6 +50,7 @@ export function SaveFilterSegmentButton() {
columnSizing,
selectedSegment,
canSaveSegment,
isSegmentEnabled,
setSegmentId,
pageSize,
searchTerm,
@@ -161,6 +162,14 @@ export function SaveFilterSegmentButton() {
setIsOpen(open);
};
if (!isSegmentEnabled) {
return (
<Button StartIcon="bookmark" color="secondary" disabled>
{t("save")}
</Button>
);
}
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
@@ -5,9 +5,9 @@ import type { Header, Table, ColumnSizingState } from "@tanstack/react-table";
import debounce from "lodash/debounce";
import { useCallback, useEffect, useRef, useMemo } from "react";
import { useDebouncedWidth, useDataTable } from "../hooks";
import { useDebouncedWidth, useDataTable } from ".";
type UsePersistentColumnResizingProps<TData> = {
type UseColumnResizingProps<TData> = {
enabled: boolean;
table: Table<TData>;
tableContainerRef: React.RefObject<HTMLDivElement>;
@@ -81,11 +81,11 @@ function getPartialColumnSizing(columnSizing: ColumnSizingState, columnsToExtrac
}, {} as ColumnSizingState);
}
export function usePersistentColumnResizing<TData>({
export function useColumnResizing<TData>({
enabled,
table,
tableContainerRef,
}: UsePersistentColumnResizingProps<TData>) {
}: UseColumnResizingProps<TData>) {
const initialized = useRef(false);
const columnSizing = useRef<ColumnSizingState>({});
const initialColumnSizing = useRef<ColumnSizingState>({});
@@ -1,33 +1,12 @@
import type { SortingState, VisibilityState, ColumnSizingState } from "@tanstack/react-table";
// eslint-disable-next-line no-restricted-imports
import { isEqual } from "lodash";
import { useCallback, useMemo, useEffect } from "react";
import { trpc } from "@calcom/trpc/react";
import { type ActiveFilters, ZSegmentStorage } from "./types";
import { ZSegmentStorage, type UseSegments } from "../lib/types";
type UseSegmentsProps = {
tableIdentifier: string;
activeFilters: ActiveFilters;
sorting: SortingState;
columnVisibility: VisibilityState;
columnSizing: ColumnSizingState;
pageSize: number;
searchTerm: string;
defaultPageSize: number;
segmentId: number;
setSegmentId: (segmentId: number | null) => void;
setActiveFilters: (activeFilters: ActiveFilters) => void;
setSorting: (sorting: SortingState) => void;
setColumnVisibility: (columnVisibility: VisibilityState) => void;
setColumnSizing: (columnSizing: ColumnSizingState) => void;
setPageSize: (pageSize: number) => void;
setPageIndex: (pageIndex: number) => void;
setSearchTerm: (searchTerm: string | null) => void;
};
export function useSegments({
export const useSegments: UseSegments = ({
tableIdentifier,
activeFilters,
sorting,
@@ -45,7 +24,7 @@ export function useSegments({
setPageSize,
setPageIndex,
setSearchTerm,
}: UseSegmentsProps) {
}) => {
const { data: segments, isFetching: isFetchingSegments } = trpc.viewer.filterSegments.list.useQuery({
tableIdentifier,
});
@@ -132,7 +111,7 @@ export function useSegments({
defaultPageSize,
]);
const setSegmentIdAndSaveToLocalStorage = useCallback(
const setAndPersistSegmentId = useCallback(
(segmentId: number | null) => {
setSegmentId(segmentId);
saveSegmentToLocalStorage({ tableIdentifier, segmentId });
@@ -144,9 +123,10 @@ export function useSegments({
segments: segments ?? [],
selectedSegment,
canSaveSegment,
setSegmentIdAndSaveToLocalStorage,
setAndPersistSegmentId,
isSegmentEnabled: true,
};
}
};
const LOCAL_STORAGE_KEY = "data-table:segments";
@@ -0,0 +1,14 @@
// eslint-disable-next-line no-restricted-imports
import { noop } from "lodash";
import { type UseSegments } from "../lib/types";
export const useSegmentsNoop: UseSegments = ({}) => {
return {
segments: [],
selectedSegment: undefined,
canSaveSegment: false,
setAndPersistSegmentId: noop,
isSegmentEnabled: false,
};
};
+1 -1
View File
@@ -2,5 +2,5 @@ export * from "./components";
export * from "./lib/types";
export * from "./lib/utils";
export * from "./DataTableProvider";
export * from "./lib/resizing";
export * from "./hooks/useColumnResizing";
export * from "./hooks";
+34 -5
View File
@@ -1,7 +1,10 @@
import type { SortingState, ColumnSort, VisibilityState, ColumnSizingState } from "@tanstack/react-table";
import { z } from "zod";
import type { IconName } from "@calcom/ui/components/icon";
export type { SortingState } from "@tanstack/react-table";
export enum ColumnFilterType {
SINGLE_SELECT = "ss",
MULTI_SELECT = "ms",
@@ -200,11 +203,7 @@ export type TypedColumnFilter<T extends ColumnFilterType> = {
value: FilterValue<T>;
};
export type Sorting = {
id: string;
desc: boolean;
};
export type SortingState = Sorting[];
export type Sorting = ColumnSort;
export const ZSorting = z.object({
id: z.string(),
@@ -267,3 +266,33 @@ export const ZSegmentStorage = z.record(
segmentId: z.number(),
})
) satisfies z.ZodType<SegmentStorage>;
export type UseSegments = (props: UseSegmentsProps) => UseSegmentsReturn;
type UseSegmentsProps = {
tableIdentifier: string;
activeFilters: ActiveFilters;
sorting: SortingState;
columnVisibility: VisibilityState;
columnSizing: ColumnSizingState;
pageSize: number;
searchTerm: string;
defaultPageSize: number;
segmentId: number;
setSegmentId: (segmentId: number | null) => void;
setActiveFilters: (activeFilters: ActiveFilters) => void;
setSorting: (sorting: SortingState) => void;
setColumnVisibility: (columnVisibility: VisibilityState) => void;
setColumnSizing: (columnSizing: ColumnSizingState) => void;
setPageSize: (pageSize: number) => void;
setPageIndex: (pageIndex: number) => void;
setSearchTerm: (searchTerm: string | null) => void;
};
type UseSegmentsReturn = {
segments: FilterSegmentOutput[];
selectedSegment: FilterSegmentOutput | undefined;
canSaveSegment: boolean;
setAndPersistSegmentId: (segmentId: number | null) => void;
isSegmentEnabled: boolean;
};
@@ -22,6 +22,7 @@ import {
DataTableFilters,
DataTableSegment,
} from "@calcom/features/data-table";
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
import ServerTrans from "@calcom/lib/components/ServerTrans";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
@@ -63,7 +64,7 @@ interface OutOfOfficeEntry {
export default function OutOfOfficeEntriesList() {
return (
<DataTableProvider>
<DataTableProvider useSegments={useSegments}>
<OutOfOfficeEntriesListContent />
</DataTableProvider>
);
@@ -14,6 +14,7 @@ import {
useColumnFilters,
useDataTable,
} from "@calcom/features/data-table";
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
@@ -58,7 +59,10 @@ type PlatformManagedUsersTableProps = {
export function PlatformManagedUsersTable(props: PlatformManagedUsersTableProps) {
return (
<DataTableProvider defaultPageSize={25} tableIdentifier={`platform-managed-users-${props.oAuthClientId}`}>
<DataTableProvider
useSegments={useSegments}
defaultPageSize={25}
tableIdentifier={`platform-managed-users-${props.oAuthClientId}`}>
<UserListTableContent {...props} />
</DataTableProvider>
);
@@ -20,6 +20,7 @@ import {
convertFacetedValuesToMap,
useDataTable,
} from "@calcom/features/data-table";
import { useSegments } from "@calcom/features/data-table/hooks/useSegments";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { WEBAPP_URL } from "@calcom/lib/constants";
import {
@@ -104,7 +105,7 @@ function reducer(state: UserTableState, action: UserTableAction): UserTableState
export function UserListTable() {
return (
<DataTableProvider defaultPageSize={25}>
<DataTableProvider useSegments={useSegments} defaultPageSize={25}>
<UserListTableContent />
</DataTableProvider>
);