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 pagetotalItems: number
- Total number of itemsisLoading?: boolean
- Loading statepageSizeOptions?: 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 navigationSelect
,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
}
}
}