# DataTable System Guide

A comprehensive guide to using Cal.diy's DataTable system for building powerful, filterable, and paginated data tables.

## Table of Contents

1. [Overview](#overview)
2. [Quick Start](#quick-start)
3. [Core Components](#core-components)
4. [Filter System](#filter-system)
5. [Segment System](#segment-system)
6. [Pagination Modes](#pagination-modes)
7. [Toolbar Components](#toolbar-components)
8. [Advanced Usage](#advanced-usage)
9. [Real-world Examples](#real-world-examples)
10. [TypeScript Types Reference](#typescript-types-reference)
11. [Best Practices](#best-practices)

## Overview

The DataTable system is a comprehensive solution for displaying tabular data with advanced features including:

- **Advanced filtering** with 5 filter types and custom operators
- **Segment system** for saving and sharing filter configurations
- **Traditional pagination** with page-based navigation (recommended)
- **Column management** (sorting, resizing, visibility)
- **Bulk actions** and selection management
- **Full TypeScript support**
- **Alternative infinite scroll** mode (has known issues, use with caution)

### Architecture

The system consists of three main layers:

1. **DataTableProvider** - Context provider managing all table state
2. **DataTableWrapper** - UI wrapper handling pagination, toolbars, and loading states
3. **DataTable** - Core table component with optional virtualization

#### Data Flow Diagram

```
┌─────────────────────────────────────────────────────────────────┐
│                        DataTableProvider                        │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │   Table State   │  │  Filter State   │  │ Segment State   │  │
│  │ • pagination    │  │ • columnFilters │  │ • activeSegment │  │
│  │ • sorting       │  │ • searchTerm    │  │ • userSegments  │  │
│  │ • columnVis     │  │ • activeFilters │  │ • systemSegments│  │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                       DataTableWrapper                          │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │   Toolbar       │  │   Data Table    │  │   Pagination    │  │
│  │ • SearchBar     │  │ • Columns       │  │ • PageControls  │  │
│  │ • FilterBar     │  │ • Rows          │  │ • PageSize      │  │
│  │ • Segments      │  │ • Selection     │  │ • TotalCount    │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                         Backend API                             │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │ Filter Processing│  │ Data Fetching   │  │ Response Format │  │
│  │ • makeWhereClause│  │ • Prisma Query  │  │ • data[]        │  │
│  │ • makeSqlCondition│  │ • Raw SQL      │  │ • totalCount    │  │
│  │ • Column Mapping │  │ • Pagination   │  │ • meta          │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
```

#### Component Hierarchy

```
DataTableProvider (Context)
└── DataTableWrapper (UI Container)
    ├── Toolbar
    │   ├── ToolbarLeft
    │   │   ├── DataTableToolbar.SearchBar
    │   │   ├── DataTableFilters.FilterBar
    │   │   └── DataTableFilters.ColumnVisibilityButton
    │   └── ToolbarRight
    │       ├── DataTableFilters.ClearFiltersButton
    │       ├── DataTableSegment.SaveButton
    │       └── DataTableSegment.Select
    ├── DataTable (Core Table)
    │   ├── Table Header
    │   ├── Table Body (Virtualized/Standard)
    │   └── Selection Bar (Conditional)
    └── Pagination Controls
        ├── Page Navigation
        ├── Page Size Selector
        └── Total Count Display
```

## Quick Start

### Basic Setup

```tsx
// Types and utilities stay in @calcom/features/data-table
import { ColumnFilterType } from "@calcom/features/data-table";

// Hooks, contexts, and providers are in apps/web/modules/data-table
// (use ~/data-table/... imports within apps/web)
import { DataTableProvider } from "~/data-table/DataTableProvider";
import { useDataTable } from "~/data-table/hooks/useDataTable";

// UI components are in apps/web/modules/data-table/components
import { DataTableWrapper } from "~/data-table/components/DataTableWrapper";
import { DataTableFilters } from "~/data-table/components/filters";

// 1. Define your data type
type User = {
  id: number;
  name: string;
  email: string;
  role: string;
};

// 2. Create columns with filtering support
const columns = [
  {
    id: "name",
    header: "Name",
    accessorKey: "name",
    meta: {
      type: ColumnFilterType.TEXT,
    },
  },
  {
    id: "role",
    header: "Role",
    accessorKey: "role",
    meta: {
      type: ColumnFilterType.SINGLE_SELECT,
    },
  },
];

// 3. Setup the table
function UserTable() {
  const table = useReactTable({
    data: users,
    columns,
    // ... other table options
  });

  const pathname = usePathname();
  const tableIdentifier = "hard-coded idenfidier"; // or pathname;

  return (
    <DataTableProvider tableIdentifier={tableIdentifier}>
      <DataTableWrapper
        table={table}
        paginationMode="standard"
        ToolbarLeft={
          <>
            <DataTableToolbar.SearchBar />
            <DataTableFilters.FilterBar table={table} />
          </>
        }
        ToolbarRight={
          <>
            <DataTableFilters.ClearFiltersButton />
            <DataTableSegment.SaveButton />
            <DataTableSegment.Select />
          </>
        }
      />
    </DataTableProvider>
  );
}
```

## Core Components

### DataTableProvider

The context provider that manages all table state including filters, sorting, pagination, and segments.

#### Props

```tsx
interface DataTableProviderProps {
  tableIdentifier: string; // Unique identifier for the table (throws if empty)
  children: React.ReactNode;
  useSegments?: UseSegments; // Custom segment hook
  defaultPageSize?: number; // Default: 10
  ctaContainerClassName?: string; // CSS class for CTA container
  segments?: FilterSegmentOutput[]; // Provided segments
  timeZone?: string; // Timezone for date filters
  preferredSegmentId?: SegmentIdentifier | null;
  systemSegments?: SystemFilterSegment[];
}
```

#### Context Values

The provider exposes comprehensive state management:

```tsx
type DataTableContextType = {
  // Filters
  activeFilters: ActiveFilters;
  addFilter: (columnId: string) => void;
  updateFilter: (columnId: string, value: FilterValue) => void;
  removeFilter: (columnId: string) => void;
  clearAll: (exclude?: string[]) => void;

  // Sorting
  sorting: SortingState;
  setSorting: OnChangeFn<SortingState>;

  // Column management
  columnVisibility: VisibilityState;
  setColumnVisibility: OnChangeFn<VisibilityState>;
  columnSizing: ColumnSizingState;
  setColumnSizing: OnChangeFn<ColumnSizingState>;

  // Pagination
  pageIndex: number;
  pageSize: number;
  setPageIndex: (pageIndex: number | null) => void;
  setPageSize: (pageSize: number | null) => void;
  offset: number;
  limit: number;

  // Segments
  segments: CombinedFilterSegment[];
  selectedSegment: CombinedFilterSegment | undefined;
  segmentId: SegmentIdentifier | null;
  setSegmentId: (id: SegmentIdentifier | null) => void;
  canSaveSegment: boolean;
  isSegmentEnabled: boolean;

  // Search
  searchTerm: string;
  setSearchTerm: (searchTerm: string | null) => void;
};
```

### DataTableWrapper

The main wrapper component that handles UI concerns, pagination, and toolbar layout.

#### Props

```tsx
type DataTableWrapperProps<TData> = {
  table: ReactTableType<TData>;
  testId?: string;
  bodyTestId?: string;
  isPending?: boolean;
  totalRowCount?: number;
  variant?: "default" | "compact";
  className?: string;
  containerClassName?: string;
  headerClassName?: string;
  rowClassName?: string;
  children?: React.ReactNode;
  tableContainerRef?: React.RefObject<HTMLDivElement>;
  onRowMouseclick?: (row: Row<TData>) => void;

  // Toolbar slots
  ToolbarLeft?: React.ReactNode;
  ToolbarRight?: React.ReactNode;

  // Loading states
  EmptyView?: React.ReactNode;
  LoaderView?: React.ReactNode;
} & ( // Infinite pagination
  | {
      paginationMode: "infinite";
      hasNextPage: boolean;
      fetchNextPage: () => void;
      isFetching: boolean;
    }
  // Standard pagination
  | {
      paginationMode: "standard";
      hasNextPage?: never;
      fetchNextPage?: never;
      isFetching?: never;
    }
);
```

### DataTable

The core table component with column resizing and pinning support.

#### Key Features

- **Column resizing** - Drag to resize columns
- **Column pinning** - Pin columns to left/right
- **Responsive design** - Adapts to mobile screens
- **Accessibility** - Full keyboard navigation support
- **Optional virtualization** - Available for infinite mode (use with caution)

## Separator Rows

The DataTable supports separator rows that act as visual group headers between regular data rows. Separators are useful for grouping related data and providing visual organization.

### Type Definitions

```tsx
export type SeparatorRow = {
  type: "separator";
  label: string;
  className?: string;
};

export type DataTableRow<TData> = TData | SeparatorRow;

export function isSeparatorRow<TData>(row: DataTableRow<TData>): row is SeparatorRow {
  return typeof row === "object" && row !== null && "type" in row && row.type === "separator";
}
```

### Usage

Separator rows are defined in your data array alongside regular rows:

```tsx
const data = [
  { type: "separator", label: "Active Users" },
  { id: 1, name: "Alice", status: "active" },
  { id: 2, name: "Bob", status: "active" },
  { type: "separator", label: "Inactive Users" },
  { id: 3, name: "Charlie", status: "inactive" },
];

<DataTableWrapper
  table={table}
  hideSeparatorsOnSort={true}
  separatorClassName="custom-separator"
  // ... other props
/>;
```

### Configuration Options

#### Props

- `hideSeparatorsOnSort?: boolean` (default: `true`) - Hide separators when sorting is active
- `hideSeparatorsOnFilter?: boolean` (default: `false`) - Hide separators when filtering is active
- `separatorClassName?: string` - Global CSS class applied to all separator rows

#### Per-Separator Styling

Each separator can have its own styling:

```tsx
const data = [
  {
    type: "separator",
    label: "Active Users",
    className: "bg-green-50 text-green-900",
  },
  { id: 1, name: "Alice" },
  {
    type: "separator",
    label: "Inactive Users",
    className: "bg-red-50 text-red-900",
  },
  { id: 2, name: "Bob" },
];
```

### Behavior with Sorting and Filtering

**Default Behavior:**

- Separators are visible when no sorting/filtering is applied
- Separators automatically hide when sorting is applied (if `hideSeparatorsOnSort` is true)
- Separators reappear when sorting is cleared

**Why Hide on Sort?**
When users sort by a column, the logical grouping may no longer make sense. For example, if you group by status but then sort by name, the status groups become meaningless. Hiding separators during sorting provides a cleaner, more intuitive experience.

**Custom Behavior:**
You can disable automatic hiding by setting `hideSeparatorsOnSort={false}` and handle separator positioning in your data preparation logic.

### Styling

Separator rows use the same styling as table headers and span the full width of the table:

```css
.separator-row {
  display: flex;
  width: 100%;
  border: none;
}

.separator-row > div {
  background-color: var(--muted);
  font-weight: 600;
  color: var(--emphasis);
  padding: 0.5rem 0.75rem;
}
```

You can customize styling using:

- `separatorClassName` prop for global styling
- `className` property on individual separator objects
- CSS targeting `.separator-row` class

### Example: User Management with Status Groups

```tsx
function UserTable() {
  const data = useMemo(() => {
    const users = getUsers();
    const groupedUsers = [];

    // Add active users group
    const activeUsers = users.filter((u) => u.status === "active");
    if (activeUsers.length > 0) {
      groupedUsers.push({ type: "separator", label: "Active Users" });
      groupedUsers.push(...activeUsers);
    }

    // Add inactive users group
    const inactiveUsers = users.filter((u) => u.status === "inactive");
    if (inactiveUsers.length > 0) {
      groupedUsers.push({ type: "separator", label: "Inactive Users" });
      groupedUsers.push(...inactiveUsers);
    }

    return groupedUsers;
  }, [users]);

  return (
    <DataTableWrapper
      table={table}
      hideSeparatorsOnSort={true}
      separatorClassName="uppercase text-xs tracking-wide"
    />
  );
}
```

## Filter System

The DataTable supports 5 filter types with various operators and options.

### Filter Types

#### 1. Single Select Filter

```tsx
{
  type: ColumnFilterType.SINGLE_SELECT,
  options: [
    { label: "Admin", value: "admin" },
    { label: "User", value: "user" },
    { label: "Guest", value: "guest" },
  ]
}
```

#### 2. Multi Select Filter

```tsx
{
  type: ColumnFilterType.MULTI_SELECT,
  options: [
    { label: "Engineering", value: "eng", section: "Departments" },
    { label: "Marketing", value: "marketing", section: "Departments" },
    { label: "Sales", value: "sales", section: "Departments" },
  ]
}
```

#### 3. Text Filter

```tsx
{
  type: ColumnFilterType.TEXT,
  textOptions: {
    allowedOperators: ["contains", "equals", "startsWith"],
    placeholder: "Search names..."
  }
}
```

**Available operators:**

- `equals` - Exact match
- `notEquals` - Not equal
- `contains` - Contains substring
- `notContains` - Does not contain
- `startsWith` - Starts with
- `endsWith` - Ends with
- `isEmpty` - Is empty
- `isNotEmpty` - Is not empty

#### 4. Number Filter

```tsx
{
  type: ColumnFilterType.NUMBER
}
```

**Available operators:**

- `eq` - Equal to
- `neq` - Not equal to
- `gt` - Greater than
- `gte` - Greater than or equal
- `lt` - Less than
- `lte` - Less than or equal

#### 5. Date Range Filter

```tsx
{
  type: ColumnFilterType.DATE_RANGE,
  dateRangeOptions: {
    range: "past", // or "custom"
    convertToTimeZone: true
  }
}
```

### Column Filter Configuration

Add filtering to columns using the `meta` property:

```tsx
const columns = [
  {
    id: "status",
    header: "Status",
    accessorKey: "status",
    meta: {
      type: ColumnFilterType.SINGLE_SELECT,
      icon: "circle-dot", // Optional icon
    } as ColumnFilterMeta,
  },
  {
    id: "createdAt",
    header: "Created",
    accessorKey: "createdAt",
    meta: {
      type: ColumnFilterType.DATE_RANGE,
      dateRangeOptions: {
        range: "past",
        convertToTimeZone: true,
      },
    } as ColumnFilterMeta,
  },
];
```

### Faceted Filters with `getFacetedUniqueValues`

For select filters that need dynamic options based on data, use `getFacetedUniqueValues` in your table configuration:

```tsx
// In your table configuration
const table = useReactTable({
  // ... other options
  getFacetedUniqueValues: (_, columnId) => () => {
    switch (columnId) {
      case "teamId":
        return convertFacetedValuesToMap(
          teams.map((team) => ({
            label: team.name,
            value: team.id,
          }))
        );
      case "role":
        return convertFacetedValuesToMap([
          { label: "Admin", value: "admin" },
          { label: "Member", value: "member" },
        ]);
      default:
        return new Map();
    }
  },
});
```

**Custom Faceted Values Hook Example:**

From `apps/web/modules/bookings/hooks/useFacetedUniqueValues.ts` (hooks live in `apps/web/modules/`):

```tsx
export function useFacetedUniqueValues() {
  const eventTypes = useEventTypes();
  const { data: teams } = trpc.viewer.teams.list.useQuery();
  const { data: members } = trpc.viewer.teams.listSimpleMembers.useQuery();

  return useCallback(
    (_: Table<any>, columnId: string) => (): Map<FacetedValue, number> => {
      if (columnId === "eventTypeId") {
        return convertFacetedValuesToMap(eventTypes || []);
      } else if (columnId === "teamId") {
        return convertFacetedValuesToMap(
          (teams || []).map((team) => ({
            label: team.name,
            value: team.id,
          }))
        );
      } else if (columnId === "userId") {
        return convertFacetedValuesToMap(
          (members || [])
            .map((member) => ({
              label: member.name,
              value: member.id,
            }))
            .filter((option): option is { label: string; value: number } => Boolean(option.label))
        );
      }
      return new Map<FacetedValue, number>();
    },
    [eventTypes, teams, members]
  );
}
```

**Usage in Table Configuration:**

```tsx
// From apps/web/modules/users/components/UserTable/UserListTable.tsx
const table = useReactTable({
  // ... other options
  getFacetedUniqueValues: (_, columnId) => () => {
    if (facetedTeamValues) {
      switch (columnId) {
        case "role":
          return convertFacetedValuesToMap(facetedTeamValues.roles);
        case "teamId":
          return convertFacetedValuesToMap(facetedTeamValues.teams);
        default:
          return new Map();
      }
    }
    return new Map();
  },
});
```

### Filter Components

#### FilterBar

Displays active filters and add filter button:

```tsx
<DataTableFilters.FilterBar table={table} />
```

#### Individual Filter Components

```tsx
// Add filter button
<DataTableFilters.AddFilterButton table={table} />

// Active filters display
<DataTableFilters.ActiveFilters table={table} />

// Clear all filters
<DataTableFilters.ClearFiltersButton />

// Column visibility toggle
<DataTableFilters.ColumnVisibilityButton table={table} />
```

## Segment System

Segments allow users to save and share filter configurations. There are two types:

**Important:** Filter segments are only enabled when you pass the `useSegments` prop to `DataTableProvider`:

```tsx
<DataTableProvider
  useSegments={useSegments} // Required to enable segments
>
  {/* Your table content */}
</DataTableProvider>
```

Without the `useSegments` prop, segment functionality will be disabled and segment-related components will not be available.

### System Segments

Predefined segments created by developers:

```tsx
const systemSegments: SystemFilterSegment[] = [
  {
    id: "active-users",
    name: "Active Users",
    type: "system",
    activeFilters: [
      {
        f: "status",
        v: {
          type: ColumnFilterType.SINGLE_SELECT,
          data: "active",
        },
      },
    ],
    sorting: [{ id: "lastLogin", desc: true }],
  },
];

<DataTableProvider systemSegments={systemSegments}>{/* ... */}</DataTableProvider>;
```

### User Segments

Segments saved by users with personal or team scope:

```tsx
// Personal segment (scope: "USER")
// Team segment (scope: "TEAM")
```

### Segment Components

```tsx
// Segment selector dropdown
<DataTableSegment.Select />

// Save current state as segment
<DataTableSegment.SaveButton />

// Segment management (rename, duplicate, delete)
// Available in the segment dropdown submenu
```

### Segment Permissions

- **Personal segments**: Only visible to the creator
- **Team segments**: Visible to all team members
- **System segments**: Visible to all users
- **Admin actions**: Rename/delete require admin/owner permissions

## Pagination Modes

### Standard Pagination (Recommended)

Traditional page-based pagination is the recommended approach:

```tsx
<DataTableWrapper paginationMode="standard" totalRowCount={totalCount} table={table} />
```

**Features:**

- Page numbers and navigation
- Configurable page sizes
- Total count display
- Reliable performance
- Better user experience
- No virtualization issues

**Why Standard Mode is Preferred:**
Standard pagination provides a more predictable and stable user experience. It avoids the complexity and potential issues associated with virtualized infinite scrolling.

### Infinite Pagination (Use with Caution)

Alternative infinite scroll mode with known limitations:

```tsx
<DataTableWrapper
  paginationMode="infinite"
  hasNextPage={hasNextPage}
  fetchNextPage={fetchNextPage}
  isFetching={isFetching}
  table={table}
/>
```

**Features:**

- Automatic loading on scroll
- Virtualized rendering
- Fixed container height (80dvh)

**Known Issues:**

- Virtualized infinite loading has several problems
- Can cause performance and UX issues
- Standard mode was introduced to address these problems
- Use only when absolutely necessary and with thorough testing

## Toolbar Components

### DataTableToolbar

Container and utility components for table toolbars:

```tsx
// Toolbar container
<DataTableToolbar.Root>
  <DataTableToolbar.SearchBar />
  <DataTableToolbar.CTA color="primary" StartIcon="plus">
    Add User
  </DataTableToolbar.CTA>
</DataTableToolbar.Root>

// Search input with debounced updates
<DataTableToolbar.SearchBar className="max-w-48" />

// Clear filters button (auto-hides when no filters)
<DataTableToolbar.ClearFiltersButton />

// Custom action button
<DataTableToolbar.CTA
  color="secondary"
  StartIcon="download"
  onClick={handleExport}
>
  Export
</DataTableToolbar.CTA>
```

### DataTableSelectionBar

For bulk actions when rows are selected:

```tsx
{
  numberOfSelectedRows > 0 && (
    <DataTableSelectionBar.Root>
      <p>{t("number_selected", { count: numberOfSelectedRows })}</p>

      <DataTableSelectionBar.Button color="destructive" icon="trash-2" onClick={handleBulkDelete}>
        Delete Selected
      </DataTableSelectionBar.Button>
    </DataTableSelectionBar.Root>
  );
}
```

## Advanced Usage

### Server-Side Operations

The DataTable system is designed for **server-side filtering, sorting, and pagination** with standard pagination. This approach is necessary because we only fetch a limited number of items per page, unlike the previous infinite scrolling approach that cached large amounts of data and could filter on the client side.

#### Basic Server-Side Pattern

```tsx
// Hooks are imported from ~/data-table/hooks/ within apps/web
import { useDataTable } from "~/data-table/hooks/useDataTable";
import { useColumnFilters } from "~/data-table/hooks/useColumnFilters";

// Get current table state for API calls
const { limit, offset, sorting } = useDataTable();
const columnFilters = useColumnFilters();

// Use in your API call
const { data } = trpc.users.list.useQuery({
  limit,
  offset,
  sorting,
  filters: columnFilters,
});
```

#### Prisma Where Clause Construction

For Prisma-based backends, extract individual filters and build typed where conditions:

```tsx
// From packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
const roleFilter = filters.find((filter) => filter.id === "role") as
  | TypedColumnFilter<ColumnFilterType.MULTI_SELECT>
  | undefined;
const teamFilter = filters.find((filter) => filter.id === "teams") as
  | TypedColumnFilter<ColumnFilterType.MULTI_SELECT>
  | undefined;
const lastActiveAtFilter = filters.find((filter) => filter.id === "lastActiveAt") as
  | TypedColumnFilter<ColumnFilterType.DATE_RANGE>
  | undefined;

const whereClause: Prisma.MembershipWhereInput = {
  user: {
    ...(teamFilter && {
      teams: {
        some: {
          team: makeWhereClause({
            columnName: "name",
            filterValue: teamFilter.value,
          }),
        },
      },
    }),
    ...(lastActiveAtFilter &&
      makeWhereClause({
        columnName: "lastActiveAt",
        filterValue: lastActiveAtFilter.value,
      })),
  },
  teamId: organizationId,
  ...(roleFilter &&
    makeWhereClause({
      columnName: "role",
      filterValue: roleFilter.value,
    })),
};
```

#### Raw SQL Optimization

For performance-critical queries, use raw SQL with `makeSqlCondition`:

```tsx
// From packages/lib/server/service/InsightsRoutingBaseService.ts
async getFilterConditions(): Promise<Prisma.Sql | null> {
  const conditions: Prisma.Sql[] = [];
  const columnFilters = this.filters.columnFilters || [];

  // Convert columnFilters array to object for easier access
  const filtersMap = columnFilters.reduce((acc, filter) => {
    acc[filter.id] = filter;
    return acc;
  }, {} as Record<string, TypedColumnFilter<ColumnFilterType>>);

  // Extract booking status order filter
  const bookingStatusOrder = filtersMap["bookingStatusOrder"];
  if (bookingStatusOrder && isMultiSelectFilterValue(bookingStatusOrder.value)) {
    const statusCondition = makeSqlCondition(bookingStatusOrder.value);
    if (statusCondition) {
      conditions.push(Prisma.sql`rfrd."bookingStatusOrder" ${statusCondition}`);
    }
  }

  // Extract booking UID filter
  const bookingUid = filtersMap["bookingUid"];
  if (bookingUid && isTextFilterValue(bookingUid.value)) {
    const uidCondition = makeSqlCondition(bookingUid.value);
    if (uidCondition) {
      conditions.push(Prisma.sql`rfrd."bookingUid" ${uidCondition}`);
    }
  }

  // Join all conditions with AND
  return conditions.reduce((acc, condition, index) => {
    if (index === 0) return condition;
    return Prisma.sql`(${acc}) AND (${condition})`;
  });
}
```

#### Advanced Parameter Manipulation

For complex cases where you need to manipulate filter data before sending to the backend, extract the logic into a separate hook:

```tsx
// apps/web/modules/insights/hooks/useInsightsRoutingParameters.ts
export function useInsightsRoutingParameters() {
  const { scope, selectedTeamId } = useInsightsOrgTeams();

  // Get date range filter and manipulate it
  const createdAtRange = useFilterValue("createdAt", ZDateRangeFilterValue)?.data;
  const startDate = useChangeTimeZoneWithPreservedLocalTime(
    useMemo(() => {
      return dayjs(createdAtRange?.startDate ?? getDefaultStartDate().toISOString())
        .startOf("day")
        .toISOString();
    }, [createdAtRange?.startDate])
  );

  // Get other column filters excluding the manipulated ones
  const columnFilters = useColumnFilters({
    exclude: ["createdAt"],
  });

  return {
    scope,
    selectedTeamId,
    startDate,
    endDate,
    columnFilters,
  };
}
```

#### Key Hooks for Server-Side Integration

- **`useColumnFilters()`** - Get applied filters for backend requests (import from `~/data-table/hooks/useColumnFilters`)
- **`useDataTable()`** - Get `limit`, `offset`, `sorting` for pagination (import from `~/data-table/hooks/useDataTable`)
- **`useFilterValue(columnId, schema)`** - Get specific filter value with validation (import from `~/data-table/hooks/useFilterValue`)
- **Custom parameter hooks** - Extract complex manipulation logic (place in `apps/web/modules/`)

#### Server Utility Functions

The DataTable system provides utility functions for both Prisma and raw SQL approaches:

- **`makeWhereClause()`** - Converts filter values to Prisma where clause objects
- **`makeSqlCondition()`** - Converts filter values to raw SQL conditions
- **`makeOrderBy()`** - Converts sorting state to Prisma orderBy format

### Custom Hooks

#### useDataTable

Access the DataTable context:

```tsx
const { activeFilters, sorting, columnVisibility, pageIndex, pageSize, searchTerm, selectedSegment } =
  useDataTable();
```

#### useColumnFilters

Get processed column filters for API calls:

```tsx
const columnFilters = useColumnFilters();
// Returns: ColumnFilter[] ready for backend consumption

// With exclusions (useful when manipulating specific filters)
const columnFilters = useColumnFilters({
  exclude: ["createdAt", "dateRange"]
});
```

#### useFilterableColumns

Extract filterable columns from table definition:

```tsx
const filterableColumns = useFilterableColumns(table);
```

### Custom Filter Components

Create custom filter implementations:

```tsx
function CustomStatusFilter({ column }: { column: Column<any> }) {
  const { updateFilter } = useDataTable();

  return (
    <Select
      onValueChange={(value) =>
        updateFilter(column.id, {
          type: ColumnFilterType.SINGLE_SELECT,
          data: value,
        })
      }>
      {/* Custom filter UI */}
    </Select>
  );
}
```

### Portal Integration

Use portals for toolbar actions:

```tsx
const { ctaContainerRef } = useDataTable();

{
  ctaContainerRef.current &&
    createPortal(
      <div className="flex gap-2">
        <Button>Custom Action</Button>
      </div>,
      ctaContainerRef.current
    );
}
```

## Real-world Examples

### Example 1: User Management Table

From `apps/web/modules/users/components/UserTable/UserListTable.tsx`:

```tsx
<DataTableWrapper<UserTableUser>
  testId="user-list-data-table"
  table={table}
  isPending={isPending}
  totalRowCount={data?.meta?.totalRowCount}
  paginationMode="standard"
  ToolbarLeft={
    <>
      <DataTableToolbar.SearchBar />
      <DataTableFilters.ColumnVisibilityButton table={table} />
      <DataTableFilters.FilterBar table={table} />
    </>
  }
  ToolbarRight={
    <>
      <DataTableFilters.ClearFiltersButton />
      <DataTableSegment.SaveButton />
      <DataTableSegment.Select />
    </>
  }>
  {/* Selection bar for bulk actions */}
  {numberOfSelectedRows > 0 && (
    <DataTableSelectionBar.Root>
      <p>{t("number_selected", { count: numberOfSelectedRows })}</p>
      <DeleteBulkUsers
        users={table.getSelectedRowModel().flatRows.map((row) => row.original)}
        onRemove={() => table.toggleAllPageRowsSelected(false)}
      />
    </DataTableSelectionBar.Root>
  )}
</DataTableWrapper>
```

### Example 2: Bookings List

From `apps/web/modules/bookings/components/BookingsList.tsx` (hooks/providers imported from `~/data-table/`):

```tsx
<DataTableWrapper
  className="mb-6"
  tableContainerRef={tableContainerRef}
  table={table}
  testId={`${status}-bookings`}
  bodyTestId="bookings"
  headerClassName="hidden"
  isPending={query.isPending}
  totalRowCount={query.data?.totalCount}
  variant="compact"
  paginationMode="standard"
  ToolbarLeft={<DataTableFilters.FilterBar table={table} />}
  ToolbarRight={
    <>
      <DataTableFilters.ClearFiltersButton />
      <DataTableSegment.SaveButton />
      <DataTableSegment.Select />
    </>
  }
  LoaderView={<SkeletonLoader />}
  EmptyView={
    <EmptyScreen
      Icon="calendar"
      headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
      description={t("no_status_bookings_yet_description")}
    />
  }
/>
```

### Example 3: Team Member List with Infinite Scroll

From `apps/web/modules/ee/teams/components/MemberList.tsx`:

```tsx
<DataTableWrapper
  testId="team-member-list-container"
  table={table}
  tableContainerRef={tableContainerRef}
  isPending={isPending}
  paginationMode="infinite"
  hasNextPage={hasNextPage}
  fetchNextPage={fetchNextPage}
  isFetching={isFetching}
  ToolbarLeft={
    <>
      <DataTableToolbar.SearchBar />
      <DataTableFilters.FilterBar table={table} />
    </>
  }
  ToolbarRight={
    <>
      <DataTableFilters.ClearFiltersButton />
      <DataTableSegment.SaveButton />
      <DataTableSegment.Select />
    </>
  }>
  {/* Bulk selection and actions */}
  {numberOfSelectedRows > 0 && (
    <DataTableSelectionBar.Root>
      <TeamListBulkAction table={table} />
      <MassAssignAttributesBulkAction table={table} filters={columnFilters} />
    </DataTableSelectionBar.Root>
  )}
</DataTableWrapper>
```

## TypeScript Types Reference

### Core Types

```tsx
// Filter value types
type FilterValue =
  | SingleSelectFilterValue
  | MultiSelectFilterValue
  | TextFilterValue
  | NumberFilterValue
  | DateRangeFilterValue;

// Active filter structure
type ActiveFilter = {
  f: string; // field/column ID
  v?: FilterValue; // filter value
};

// Segment types
type SegmentIdentifier = { id: string; type: "system" } | { id: number; type: "user" };

// Column filter metadata
type ColumnFilterMeta = {
  type: ColumnFilterType;
  icon?: IconName;
  dateRangeOptions?: DateRangeFilterOptions;
  textOptions?: TextFilterOptions;
};
```

### Filter Value Schemas

```tsx
// Single select
type SingleSelectFilterValue = {
  type: ColumnFilterType.SINGLE_SELECT;
  data: string | number;
};

// Multi select
type MultiSelectFilterValue = {
  type: ColumnFilterType.MULTI_SELECT;
  data: Array<string | number>;
};

// Text filter
type TextFilterValue = {
  type: ColumnFilterType.TEXT;
  data: {
    operator: TextFilterOperator;
    operand: string;
  };
};

// Number filter
type NumberFilterValue = {
  type: ColumnFilterType.NUMBER;
  data: {
    operator: NumberFilterOperator;
    operand: number;
  };
};

// Date range filter
type DateRangeFilterValue = {
  type: ColumnFilterType.DATE_RANGE;
  data: {
    startDate: string | null;
    endDate: string | null;
    preset: string;
  };
};
```

### Segment Types

```tsx
// System segment (predefined)
type SystemFilterSegment = {
  id: string;
  name: string;
  type: "system";
  activeFilters: ActiveFilters;
  sorting?: SortingState;
  columnVisibility?: Record<string, boolean>;
  columnSizing?: Record<string, number>;
  perPage?: number;
  searchTerm?: string | null;
};

// User segment (saved by users)
type UserFilterSegment = FilterSegmentOutput & {
  type: "user";
};

// Combined segment type
type CombinedFilterSegment = SystemFilterSegmentInternal | UserFilterSegment;
```

## Best Practices

### Performance

1. **Use standard pagination** for most use cases (recommended)
2. **Implement proper memoization** for column definitions
3. **Debounce search inputs** (handled automatically by DataTableToolbar.SearchBar)
4. **Consider infinite mode only for specific cases** where traditional pagination isn't suitable, but be aware of potential issues

### State Management

1. **Provide unique tableIdentifier** for each table instance
2. **Use segments** for complex filter combinations
3. **Persist user preferences** via the segment system
4. **Clear filters appropriately** when changing contexts

### User Experience

1. **Provide loading states** with LoaderView and EmptyView
2. **Use standard pagination** for consistent user experience
3. **Include search functionality** for text-heavy data
4. **Implement bulk actions** for management interfaces
5. **Show filter counts** and active filter indicators

### Accessibility

1. **Use semantic HTML** (handled by components)
2. **Provide proper ARIA labels** for custom filters
3. **Support keyboard navigation** (built-in)
4. **Test with screen readers**

### Code Organization

```tsx
// ✅ Good: Memoize column definitions
const columns = useMemo(
  () => [
    {
      id: "name",
      header: "Name",
      accessorKey: "name",
      meta: { type: ColumnFilterType.TEXT },
    },
  ],
  []
);

// ✅ Good: Extract filter logic
const useUserFilters = () => {
  const columnFilters = useColumnFilters();
  return useMemo(() => transformFiltersForAPI(columnFilters), [columnFilters]);
};

// ✅ Good: Separate concerns
function UserTableContainer() {
  const filters = useUserFilters();
  const { data, isPending } = useUsers(filters);

  return (
    <DataTableProvider>
      <UserTable data={data} isPending={isPending} />
    </DataTableProvider>
  );
}
```

### Common Patterns

#### Filter Integration with API

```tsx
function useTableData() {
  const columnFilters = useColumnFilters();
  const { sorting, pageIndex, pageSize, searchTerm } = useDataTable();

  const queryParams = useMemo(
    () => ({
      filters: columnFilters,
      sorting,
      page: pageIndex,
      limit: pageSize,
      search: searchTerm,
    }),
    [columnFilters, sorting, pageIndex, pageSize, searchTerm]
  );

  return useQuery({
    queryKey: ["table-data", queryParams],
    queryFn: () => fetchData(queryParams),
  });
}
```

#### Custom Filter Options

```tsx
function useStatusFilterOptions() {
  const { data: statuses } = useStatuses();

  return useMemo(
    () =>
      statuses?.map((status) => ({
        label: status.name,
        value: status.id,
        section: status.category,
      })) || [],
    [statuses]
  );
}
```

#### Segment Presets

```tsx
const BOOKING_SEGMENTS: SystemFilterSegment[] = [
  {
    id: "upcoming",
    name: "Upcoming",
    type: "system",
    activeFilters: [
      {
        f: "status",
        v: { type: ColumnFilterType.SINGLE_SELECT, data: "confirmed" },
      },
      {
        f: "startTime",
        v: {
          type: ColumnFilterType.DATE_RANGE,
          data: { preset: "future", startDate: null, endDate: null },
        },
      },
    ],
    sorting: [{ id: "startTime", desc: false }],
  },
];
```

This guide covers the complete DataTable system. For specific implementation details, refer to:
- **Types, utilities, serializers, server-side code**: `packages/features/data-table/lib/`
- **Hooks, contexts, DataTableProvider**: `apps/web/modules/data-table/`
- **UI components (DataTable, DataTableWrapper, filters, etc.)**: `apps/web/modules/data-table/components/`
- **Usage examples**: Throughout `apps/web/modules/` (bookings, insights, users, etc.)
