DeployStack Docs

Frontend Pagination Implementation Guide

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

Quick Implementation

1. Service Layer

Add pagination support to your service:

// 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

<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):

pagination: {
  showing: 'Showing {start} to {end} of {total} items',
  noItems: 'No items to display',
  itemsPerPage: 'Items per page:',
  pageInfo: 'Page {current} of {total}',
  previous: 'Previous',
  next: 'Next'
}

PaginationControls Component

Props

  • currentPage: number - Current page number (1-based)
  • pageSize: number - Items per page
  • totalItems: number - Total number of items
  • isLoading?: boolean - Loading state
  • pageSizeOptions?: number[] - Available page sizes (default: [10, 20, 50, 100])

Events

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

shadcn-vue Components Used

The PaginationControls component uses these shadcn-vue components:

  • Button - For Previous/Next navigation
  • Select, SelectContent, SelectItem, SelectTrigger, SelectValue - For page size selector
  • Lucide icons: ChevronLeft, ChevronRight

Search Integration

For search functionality, conditionally show pagination:

<template>
  <!-- Show pagination only when not searching -->
  <PaginationControls
    v-if="!searchQuery && totalItems > 0"
    :current-page="currentPage"
    :page-size="pageSize"
    :total-items="totalItems"
    @page-change="handlePageChange"
    @page-size-change="handlePageSizeChange"
  />
  
  <!-- Show search results count when searching -->
  <div v-if="searchQuery" class="text-sm text-muted-foreground py-4">
    {{ t('yourFeature.pagination.showing', {
      start: filteredItems.length > 0 ? 1 : 0,
      end: filteredItems.length,
      total: filteredItems.length
    }) }}
  </div>
</template>

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:

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