# DataTable

## Overview

Powerful table built on TanStack Table v8. Supports client-side and server-side modes with a unified API, advanced filtering (badges + builder), global search, multi-column sorting, row selection, column pinning, dynamic pagination, i18n, and loading skeletons.

---

## Props

| Prop                    | Type                                  | Default                | Description                                                                             |
| ----------------------- | ------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------- |
| `columns`               | `ColumnDef<TData, TValue>[]`          | —                      | TanStack column definitions. Use `meta` to enable features.                             |
| `data`                  | `TData[]`                             | —                      | Table data.                                                                             |
| `loading`               | `boolean`                             | `false`                | Show skeleton rows.                                                                     |
| `emptyComponent`        | `ReactNode`                           | `undefined`            | Rendered when no rows.                                                                  |
| `className`             | `string`                              | `undefined`            | Wrapper classes.                                                                        |
| `checkable`             | `boolean`                             | `false`                | Adds a left checkbox column for row selection.                                          |
| `rowSelection`          | `Record<string, boolean>`             | `{}`                   | Controlled selection map.                                                               |
| `onRowSelectionChange`  | `OnChangeFn<Record<string, boolean>>` | `undefined`            | Selection change handler (controlled selection).                                        |
| `onCheckedRowsChange`   | `(checkedRows: TData[]) => void`      | `undefined`            | Returns selected row values (requires `checkable`).                                     |
| `actions`               | `DataTableActions[]`                  | `[]`                   | Actions for the actions row.                                                            |
| `customComponentsLeft`  | `ReactNode`                           | `undefined`            | Custom components rendered on the left of the actions row.                              |
| `customComponentsRight` | `ReactNode`                           | `undefined`            | Custom components rendered on the right of the actions row.                             |
| `hidePagination`        | `boolean`                             | `false`                | Hide pagination UI.                                                                     |
| `hideActionsRow`        | `boolean`                             | `false`                | Hide actions row.                                                                       |
| `i18n`                  | `DataTableI18n`                       | `defaultDataTableI18n` | Internationalization strings (sorting/filtering popovers included).                     |
| `maxSortedColumns`      | `number`                              | `2`                    | Max number of sorted columns.                                                           |
| `initialState`          | `DataTableState<TData>`               | `undefined`            | Initial filters, sorting, pagination, optional `columnVisibility`, and searchbar value. |
| `serverMode`            | `boolean`                             | `false`                | Enable server-side mode (client-side filtering/sorting when `false`).                   |
| `serverConfig`          | `DataTableServerConfig`               | `undefined`            | `{ totalItems, onStateChange, serverDebounceTime? }` handler for server mode.           |
| `disableAutoPageSize`   | `boolean`                             | `false`                | Disable auto pageSize (still updates pageIndex).                                        |
| `id`                    | `string`                              | `undefined`            | Test/accessibility hook for the root element.                                           |
| `data-testid`           | `string`                              | `undefined`            | Test identifier forwarded to the root element.                                          |
| `rowClassName`          | `string \| ((row) => string)`         | `undefined`            | Add custom CSS classes to table rows based on row data or state.                        |

---

## Column meta

Configure columns via `column.meta`:

```ts
type ColumnMeta = {
  type:
    | "string"
    | "number"
    | "boolean"
    | "date"
    | "datetime"
    | "list_single_select"
    | "list_multi_select"
    | "other";
  headerLabel?: string;
  sortable?: boolean;
  filterable?: boolean;
  searchable?: boolean;
  pinned?: "left" | "right";
  listOptions?: { value: string; label: string }[];
  cellClassName?: string | ((value: any, row: TData) => string);
  headerClassName?: string;
};
```

Notes:

- `id` must match the field key used for filtering/sorting.
- If `header` is a `ReactNode` (e.g. `header: () => <div>Nome</div>`), set `meta.headerLabel` to provide a stable text label for sorting/filtering/column-visibility UI.
- Filtering nested accessors (e.g., `"user.name"`) is not supported.

---

## Behavior

- **Filtering**: Badge filters + advanced builder with logical `_and`/`_or`; client-side application when `serverMode=false`, state emission when `serverMode=true`.
- **Global search**: Across `meta.searchable` columns; strings use `like`, arrays use `array_overlap` (mapped automatically for list single select).
- **Operators**: Full operator set per type, including `eq_null`/`n_eq_null`, list operators (`array_overlap`, `n_array_overlap`), date/time before/after, checked/unchecked.
- **Sorting**: Multi-column (limited by `maxSortedColumns`); client-side when `serverMode=false`, emitted to server otherwise.
- **Selection**: Integrated checkbox column when `checkable`.
- **Column visibility**: Columns can start hidden via `initialState.columnVisibility` and be toggled from the eye icon popover in the toolbar (supports drag-and-drop reordering).
- **Non-hideable columns**: Set `enableHiding: false` on a column to prevent it from being hidden.
- **Custom JSX headers**: Use `meta.headerLabel` when `header` is a ReactNode to provide a stable text label for menus.
- **Pinning**: `meta.pinned` supports left/right pinned columns.
- **Pagination**: Auto page size from container height; respects `disableAutoPageSize`.
- **Loading**: Skeleton rows adapt to viewport height (no hardcoded length).
- **i18n**: All labels (sorting/filtering menus included) use `DataTableI18n`.
- **Datetime**: Supports microseconds format like `2025-08-22T12:53:54.060315` and timezone-safe date handling.

---

## Server-side mode

When `serverMode=true`, the table emits state to `serverConfig.onStateChange(state)` with:

```ts
type ServerState = {
  pagination: { pageIndex: number; pageSize: number };
  filters?: IFilterState;
  computedFilter?: SearchFilter;
  computedSorting?: { sort_by: string[]; sort_order: ("asc" | "desc")[] };
};
```

- `computedFilter`/`computedSorting` are auto-generated from the UI and preserved across updates.
- Debounced emissions are configurable via `serverConfig.serverDebounceTime`.

---

## Examples

### Client mode using utilities (columns + filters + initial state)

```tsx
import * as React from "react";
import type { ColumnDef } from "@tanstack/react-table";
import {
  DataTable,
  createStringColumn,
  createNumberColumn,
  createSingleSelectColumn,
  createMultiSelectColumn,
  createActionColumn,
  createInitialState,
  createStringFilter,
  createNumberFilter,
} from "laif-ds";
import { Button } from "laif-ds";

type Person = {
  id: string;
  name: string;
  age: number;
  role: string;
  tags: string[];
  created_at: string;
  active: boolean;
};

const columns = [
  createStringColumn<Person>({
    accessorKey: "name",
    header: "Nome",
    sortable: true,
    filterable: true,
    searchable: true,
  }),
  createNumberColumn<Person>({
    accessorKey: "age",
    header: "Età",
    sortable: true,
    filterable: true,
  }),
  createSingleSelectColumn<Person>({
    accessorKey: "role",
    header: "Ruolo",
    options: ["Admin", "User", "Guest"],
    filterable: true,
    searchable: true,
  }),
  createMultiSelectColumn<Person>({
    accessorKey: "tags",
    header: "Tags",
    options: ["frontend", "backend", "devops"],
    filterable: true,
  }),
  createActionColumn<Person>({
    id: "actions",
    header: "Azioni",
    pinned: "right",
    cell: (row) => <Button size="sm">Edit {row.name}</Button>,
  }),
] satisfies ColumnDef<Person, any>[];

const initialState = createInitialState({
  filters: [
    createStringFilter("name", "name", "Nome", "like", "John"),
    createNumberFilter("age", "age", "Età", "ge", 18),
  ],
  searchbarFilter: "developer",
  sorting: [{ column: "name", order: "asc" }],
  pagination: { pageIndex: 0, pageSize: 15 },
});

const actions = [
  {
    label: "Export CSV",
    icon: "FileDown",
    onClick: () => {
      /* ... */
    },
  },
  {
    label: "Bulk Delete",
    icon: "Trash2",
    onClick: () => {
      /* ... */
    },
  },
];

export function ClientTable({ data }: { data: Person[] }) {
  return (
    <DataTable
      columns={columns}
      data={data}
      initialState={initialState}
      actions={actions}
      checkable
      onCheckedRowsChange={(rows) => console.log("checked rows", rows)}
    />
  );
}
```

### Server mode using utilities (state-driven fetch)

```tsx
import * as React from "react";
import type { ServerState } from "laif-ds";
import { DataTable, createInitialState } from "laif-ds";

export function ServerTable({
  data,
  total,
}: {
  data: Person[];
  total: number;
}) {
  const [loading, setLoading] = React.useState(false);

  const handleState = React.useCallback(async (state: ServerState) => {
    // Use state.pagination, state.computedFilter, state.computedSorting
    // to fetch from your backend
    setLoading(true);
    try {
      await fetch("/api/people", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(state),
      });
      // Update your data + total here based on response
    } finally {
      setLoading(false);
    }
  }, []);

  return (
    <DataTable
      columns={columns}
      data={data}
      loading={loading}
      serverMode
      serverConfig={{ totalItems: total, onStateChange: handleState }}
      initialState={createInitialState({
        pagination: { pageIndex: 0, pageSize: 20 },
      })}
      checkable
    />
  );
}
```

### Initial column visibility

You can start with some columns hidden and let the user control them via the column-visibility (eye) popover:

```tsx
const initialStateWithHiddenColumns = createInitialState<Person>({
  pagination: { pageIndex: 0, pageSize: 15 },
  sorting: [{ column: "name", order: "asc" }],
  columnVisibility: {
    // these columns will start hidden in the UI
    secret_field: false,
    debug_column: false,
  },
});

export function TableWithHiddenColumns({ data }: { data: Person[] }) {
  return (
    <DataTable
      columns={columns}
      data={data}
      initialState={initialStateWithHiddenColumns}
    />
  );
}
```

### Non-hideable columns

If a column must always stay visible, set `enableHiding: false` on the column definition.

```tsx
import type { ColumnDef } from "@tanstack/react-table";

const columns: ColumnDef<Person>[] = [
  {
    accessorKey: "name",
    header: "Nome",
    meta: { type: "string", sortable: true, searchable: true },
  },
  {
    accessorKey: "email",
    header: "Email",
    enableHiding: false,
    meta: { type: "string", searchable: true },
  },
];

export function TableWithNonHideableColumn({ data }: { data: Person[] }) {
  return <DataTable columns={columns} data={data} />;
}
```

### Checkbox selection column (`checkable`)

When `checkable` is enabled, the DataTable adds a left checkbox column for row selection. This column is not hideable and not draggable in the column visibility popover.

```tsx
export function TableCheckable({ data }: { data: Person[] }) {
  return <DataTable columns={columns} data={data} checkable />;
}
```

### Drag-and-drop reordering

You can reorder columns by opening the column visibility popover (eye icon) and dragging items (grip icon). The order is managed internally via table `columnOrder` state.

```tsx
export function TableWithReordering({ data }: { data: Person[] }) {
  return <DataTable columns={columns} data={data} />;
}
```

### ReactNode headers + `meta.headerLabel`

If you use a `ReactNode` header, set `meta.headerLabel` so that sorting/filtering/visibility menus can show a readable label.

```tsx
import type { ColumnDef } from "@tanstack/react-table";

const columns: ColumnDef<Person>[] = [
  {
    accessorKey: "name",
    header: () => <div>Nome</div>,
    meta: {
      type: "string",
      headerLabel: "Nome",
      sortable: true,
      filterable: true,
      searchable: true,
    },
  },
];
```

### Utility recipes (pinning, list options, quick filters)

```tsx
import {
  pinColumns,
  updateColumnListOptions,
  toSelectOptions,
  createFilterBadges,
  createStringFilter,
  createNumberFilter,
} from "laif-ds";

// Pin columns to left/right
const pinnedColumns = pinColumns(columns, {
  left: ["name"],
  right: ["actions"],
});

// Update list options dynamically (e.g., fetched from API)
const roleOptions = toSelectOptions(["Admin", "User", "Guest", "Manager"]);
const columnsWithUpdatedRoles = updateColumnListOptions(
  columns,
  "role",
  roleOptions,
);

// Build multiple filter badges quickly
const quickFilters = createFilterBadges([
  {
    columnId: "name",
    columnAccessorKey: "name",
    columnLabel: "Nome",
    columnType: "string",
    operator: "like",
    value: "Jane",
  },
  {
    columnId: "age",
    columnAccessorKey: "age",
    columnLabel: "Età",
    columnType: "number",
    operator: "ge",
    value: 30,
  },
]);
```

---

## Notes

- **Performance**: Client mode filters/sorts in-memory; use server mode for large datasets.
- **UX**: Page size auto-adapts; use `disableAutoPageSize` to manage it manually.
- **Internationalization**: Provide custom `i18n` for localized actions and labels.
