# AdvancedTable

Server-paginated data table with a column-visibility ("View") menu. Built on Mantine `Table`. Portable — two files, no app-specific imports.

## Files

- `AdvancedTable.tsx` — the component.
- `useServerTable.ts` — small hook for page-index + search-query state (optional, but pairs with it).

To use in another project, copy both files as-is into that project and export them from your UI barrel (or import by relative path).

## Dependencies

```
@mantine/core       (tested on ^8)
@tabler/icons-react  (IconRefresh, IconEye, IconInbox)
react-i18next        (useTranslation)
react                (>=17, uses hooks)
```

If the target project doesn't use `react-i18next`, replace the `t(key, fallback)` calls with plain strings — the component only reads the fallback text, translation is not load-bearing.

## Install

```bash
npm install @mantine/core @tabler/icons-react react-i18next
```

Mantine must already be set up with `MantineProvider` in the app root — this component does not wrap one.

## Copy the source

Copy `AdvancedTable.tsx` and `useServerTable.ts` into the new project (e.g. `src/components/table/`). No modifications needed unless you're renaming the i18n keys.

## API

### `AdvancedColumn<T>`

| Field | Type | Required | Notes |
|---|---|---|---|
| `header` | `ReactNode` | yes | Column heading, also used as the label in the View menu. |
| `accessorKey` | `string` | no | Dot-path into the row (e.g. `"expectation.name"`), used when `cell` is omitted. |
| `cell` | `(ctx: { row: { original: T }; value: unknown }) => ReactNode` | no | Custom cell renderer. Takes priority over `accessorKey`. |
| `size` | `number` | no | Column width in px. |
| `align` | `'left' \| 'center' \| 'right'` | no | Text alignment for header + cells. |
| `enabled` | `boolean` | no | Whether the column starts **visible**. Default `true`. Toggled at runtime via the View menu. |

### `AdvancedTable<T>` props

| Prop | Type | Required | Notes |
|---|---|---|---|
| `columns` | `AdvancedColumn<T>[]` | yes | |
| `data` | `T[]` | yes | Rows for the **current page** only — not the full dataset. |
| `tableName` | `string` | yes | Shown as the table title. |
| `itemCount` | `number` | yes | Total row count on the server. Drives pagination and the count badge — not `data.length`. |
| `pageIndex` | `number` | yes | 0-based current page. |
| `onPageChange` | `(pageIndex: number) => void` | yes | |
| `pageSize` | `number` | no | Default `10`. Pagination only renders when `itemCount > pageSize`. |
| `onPageSizeChange` | `(pageSize: number) => void` | no | Shows a page-size `<Select>` (10/20/30/40/50 by default) next to the pagination when given. |
| `pageSizeOptions` | `number[]` | no | Options for the page-size select. Default `[10, 20, 30, 40, 50]`. |
| `refresh` | `() => void` | no | Shows a Refresh button when provided. |
| `onSearchChange` | `(q: string) => void` | no | Reserved for a search box; not currently rendered by the component itself (wire your own input and call this, or drive `useServerTable`'s `setQ`). |
| `isLoading` | `boolean` | no | Shows a loader row; also spins the Refresh button. |
| `emptyText` | `string` | no | Message when `data` is empty. |

Rows should have an `id: string \| number` field — used as the React key (falls back to row index if absent).

### `useServerTable(opts?)`

```ts
const { pageIndex, setPageIndex, q, setQ, pageSize, setPageSize, skip, take } = useServerTable({ pageSize: 10 });
```

Centralizes page-index + search-query state for a server-paginated list. `setQ` and `setPageSize` both reset `pageIndex` back to 0. `skip`/`take` are ready to drop into an offset-based API call. Wire `setPageSize` into `AdvancedTable`'s `onPageSizeChange` to expose the page-size select.

## Behavior notes

- **Column visibility** is local UI state (`useState`), re-initialized from each column's `enabled` on mount — it does not persist across reloads or sync back to the caller.
- At least one column always stays visible; the View menu disables unchecking the last one.
- The View menu closes only via outside click (`closeOnItemClick={false}`), so multiple columns can be toggled per open.

## Usage

### Basic

```tsx
import { AdvancedTable, type AdvancedColumn } from './table/AdvancedTable';

interface User {
  id: string;
  name: string;
  email: string;
}

const columns: AdvancedColumn<User>[] = [
  { header: 'Name', accessorKey: 'name' },
  { header: 'Email', accessorKey: 'email', enabled: true },
];

<AdvancedTable
  columns={columns}
  data={users}
  tableName="Users"
  itemCount={users.length}
  pageIndex={0}
  onPageChange={() => {}}
/>
```

### Server-paginated, with refresh and a custom action column

```tsx
import { AdvancedTable, useServerTable, type AdvancedColumn } from './table/AdvancedTable';

function UsersTable() {
  const { pageIndex, setPageIndex, skip, take } = useServerTable({ pageSize: 10 });
  const { data, isFetching, refetch } = useGetUsersQuery({ skip, take });

  const users = data?.items ?? [];
  const totalCount = data?.total ?? 0;

  const columns: AdvancedColumn<User>[] = [
    { header: 'Name', accessorKey: 'name' },
    { header: 'Email', accessorKey: 'email' },
    {
      header: 'Status',
      align: 'center',
      cell: ({ row }) => (row.original.active ? 'Active' : 'Inactive'),
    },
    {
      header: '',
      size: 80,
      cell: ({ row }) => (
        <ActionIcon onClick={() => onEdit(row.original)}>
          <IconEdit size={14} />
        </ActionIcon>
      ),
    },
  ];

  return (
    <AdvancedTable
      columns={columns}
      data={users}
      tableName="Users"
      itemCount={totalCount}
      pageIndex={pageIndex}
      onPageChange={setPageIndex}
      pageSize={10}
      refresh={refetch}
      isLoading={isFetching}
      emptyText="No users found"
    />
  );
}
```

Real reference implementation: `apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx` (`ProfessionTab`).
