> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Pagination Implementation Guide

> Developer guide for implementing pagination in DeployStack frontend using the PaginationControls component.

This guide shows developers how to add pagination to any data table in the DeployStack frontend.

## Pagination Style

The pagination follows the shadcn-vue DataTable pattern:

**Without selection (default):**

```
                                    [Rows per page ▼]  Page 1 of 7  [<<] [<] [>] [>>]
```

**With selection enabled:**

```
0 of 68 row(s) selected.           [Rows per page ▼]  Page 1 of 7  [<<] [<] [>] [>>]
```

* **Selection info** (left, optional) - "X of Y row(s) selected." Only shown when selection props are provided
* **Rows per page selector** (right) - Dropdown with options: 10, 20, 30, 40, 50 (hidden on mobile)
* **Page indicator** (right) - "Page X of Y"
* **Navigation buttons** (right) - First, Previous, Next, Last (First/Last hidden on mobile)

## Quick Implementation

### 1. Service Layer

Add pagination support to your service:

```typescript theme={null}
// services/yourService.ts
export interface PaginationParams {
  limit?: number
  offset?: number
}

export interface PaginationMeta {
  total: number
  limit: number
  offset: number
  has_more: boolean
}

export interface PaginatedResponse<T> {
  items: T[]
  pagination: PaginationMeta
}

static async getItemsPaginated(
  filters?: ItemFilters,
  pagination?: PaginationParams
): Promise<PaginatedResponse<Item>> {
  const url = new URL(`${this.baseUrl}/api/items`)

  // Add filters and pagination params
  if (filters) {
    Object.entries(filters).forEach(([key, value]) => {
      if (value !== undefined) url.searchParams.append(key, String(value))
    })
  }

  if (pagination) {
    if (pagination.limit) url.searchParams.append('limit', String(pagination.limit))
    if (pagination.offset) url.searchParams.append('offset', String(pagination.offset))
  }

  const response = await fetch(url.toString(), {
    method: 'GET',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' }
  })

  const data = await response.json()

  return {
    items: data.data.items,
    pagination: data.data.pagination
  }
}
```

### 2. Component Implementation

```vue theme={null}
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import PaginationControls from '@/components/ui/pagination/PaginationControls.vue'
import { YourService, type PaginationMeta } from '@/services/yourService'

// Pagination state
const currentPage = ref(1)
const pageSize = ref(20)
const totalItems = ref(0)
const items = ref([])
const isLoading = ref(false)

// Fetch data with pagination
async function fetchItems() {
  isLoading.value = true
  try {
    const offset = (currentPage.value - 1) * pageSize.value
    const response = await YourService.getItemsPaginated(
      filters.value,
      { limit: pageSize.value, offset }
    )

    items.value = response.items
    totalItems.value = response.pagination.total
  } finally {
    isLoading.value = false
  }
}

// Event handlers
async function handlePageChange(page: number) {
  currentPage.value = page
  await fetchItems()
}

async function handlePageSizeChange(newPageSize: number) {
  pageSize.value = newPageSize
  currentPage.value = 1
  await fetchItems()
}

onMounted(() => fetchItems())
</script>

<template>
  <div class="space-y-4">
    <!-- Your data table -->
    <YourTableComponent :items="items" />

    <!-- Pagination controls -->
    <PaginationControls
      v-if="totalItems > 0"
      :current-page="currentPage"
      :page-size="pageSize"
      :total-items="totalItems"
      :is-loading="isLoading"
      @page-change="handlePageChange"
      @page-size-change="handlePageSizeChange"
    />
  </div>
</template>
```

### 3. Add Translations

Add to your i18n file (e.g., `i18n/locales/en/yourFeature.ts`):

```typescript theme={null}
pagination: {
  rowsPerPage: 'Rows per page',
  pageInfo: 'Page {current} of {total}',
  firstPage: 'Go to first page',
  previousPage: 'Go to previous page',
  nextPage: 'Go to next page',
  lastPage: 'Go to last page',
  rowsSelected: '{selected} of {total} row(s) selected.'  // Only needed if using selection
}
```

## PaginationControls Component

### Props

| Prop              | Type       | Required | Default                | Description                                         |
| ----------------- | ---------- | -------- | ---------------------- | --------------------------------------------------- |
| `currentPage`     | `number`   | Yes      | -                      | Current page number (1-based)                       |
| `pageSize`        | `number`   | Yes      | -                      | Items per page                                      |
| `totalItems`      | `number`   | Yes      | -                      | Total number of items                               |
| `isLoading`       | `boolean`  | No       | `false`                | Loading state (disables navigation)                 |
| `pageSizeOptions` | `number[]` | No       | `[10, 20, 30, 40, 50]` | Available page sizes                                |
| `selectedCount`   | `number`   | No       | -                      | Number of selected rows (enables selection display) |
| `totalRows`       | `number`   | No       | -                      | Total rows for selection display                    |

### Events

* `@page-change(page: number)` - Emitted when page changes
* `@page-size-change(pageSize: number)` - Emitted when page size changes

## With Row Selection

When your table has row selection enabled, pass the `selectedCount` and `totalRows` props to show the selection info on the left:

```vue theme={null}
<PaginationControls
  :current-page="currentPage"
  :page-size="pageSize"
  :total-items="totalItems"
  :selected-count="selectedRows.length"
  :total-rows="items.length"
  @page-change="handlePageChange"
  @page-size-change="handlePageSizeChange"
/>
```

## shadcn-vue Components Used

The `PaginationControls` component uses these shadcn-vue components:

* `Button` - For navigation buttons (icon-only, outline variant, `size-8`)
* `Label` - For "Rows per page" label
* `Select`, `SelectContent`, `SelectItem`, `SelectTrigger`, `SelectValue` - For page size selector (`w-20`)
* Lucide icons: `ChevronLeft`, `ChevronRight`, `ChevronsLeft`, `ChevronsRight`

## Responsive Behavior

The pagination is responsive:

| Element                     | Desktop | Mobile  |
| --------------------------- | ------- | ------- |
| Selection info (if enabled) | Visible | Visible |
| Rows per page selector      | Visible | Hidden  |
| Page info                   | Visible | Visible |
| First page button           | Visible | Hidden  |
| Previous page button        | Visible | Visible |
| Next page button            | Visible | Visible |
| Last page button            | Visible | Hidden  |

On mobile, the navigation buttons use `ml-auto` to push them to the right edge.

## Backend Requirements

Your backend API must support these query parameters:

* `limit` - Number of items per page (1-100)
* `offset` - Number of items to skip

And return this response format:

```json theme={null}
{
  "success": true,
  "data": {
    "items": [...],
    "pagination": {
      "total": 150,
      "limit": 20,
      "offset": 40,
      "has_more": true
    }
  }
}
```
