WIP - Added pagination for list view

This commit is contained in:
Francisco Gaona
2026-01-13 09:03:11 +01:00
parent 730fddd181
commit 47fa72451d
9 changed files with 357 additions and 22 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ref, computed, watch } from 'vue'
import {
Table,
TableBody,
@@ -22,6 +22,7 @@ interface Props {
loading?: boolean
selectable?: boolean
baseUrl?: string
totalCount?: number
}
const props = withDefaults(defineProps<Props>(), {
@@ -41,6 +42,8 @@ const emit = defineEmits<{
'sort': [field: string, direction: 'asc' | 'desc']
'search': [query: string]
'refresh': []
'page-change': [page: number, pageSize: number]
'load-more': [page: number, pageSize: number]
}>()
// State
@@ -48,12 +51,46 @@ const selectedRows = ref<Set<string>>(new Set())
const searchQuery = ref('')
const sortField = ref<string>('')
const sortDirection = ref<'asc' | 'desc'>('asc')
const currentPage = ref(1)
// Computed
const visibleFields = computed(() =>
props.config.fields.filter(f => f.showOnList !== false)
)
const pageSize = computed(() => props.config.pageSize ?? 10)
const maxFrontendRecords = computed(() => props.config.maxFrontendRecords ?? 500)
const totalRecords = computed(() =>
(props.totalCount && props.totalCount > 0)
? props.totalCount
: props.data.length
)
const useHybridPagination = computed(() => totalRecords.value > maxFrontendRecords.value)
const totalPages = computed(() => Math.max(1, Math.ceil(totalRecords.value / pageSize.value)))
const loadedPages = computed(() => Math.max(1, Math.ceil(props.data.length / pageSize.value)))
const availablePages = computed(() => {
if (useHybridPagination.value && props.totalCount && props.data.length < props.totalCount) {
return loadedPages.value
}
return totalPages.value
})
const startIndex = computed(() => (currentPage.value - 1) * pageSize.value)
const paginatedData = computed(() => {
const start = startIndex.value
const end = start + pageSize.value
return props.data.slice(start, end)
})
const pageStart = computed(() => (props.data.length === 0 ? 0 : startIndex.value + 1))
const pageEnd = computed(() => Math.min(startIndex.value + paginatedData.value.length, totalRecords.value))
const showPagination = computed(() => totalRecords.value > pageSize.value)
const canGoPrev = computed(() => currentPage.value > 1)
const canGoNext = computed(() => currentPage.value < availablePages.value)
const showLoadMore = computed(() => (
useHybridPagination.value &&
Boolean(props.totalCount) &&
props.data.length < totalRecords.value
))
const allSelected = computed({
get: () => props.data.length > 0 && selectedRows.value.size === props.data.length,
set: (val: boolean) => {
@@ -96,6 +133,28 @@ const handleSearch = () => {
const handleAction = (actionId: string) => {
emit('action', actionId, getSelectedRows())
}
const goToPage = (page: number) => {
const nextPage = Math.min(Math.max(page, 1), availablePages.value)
if (nextPage !== currentPage.value) {
currentPage.value = nextPage
emit('page-change', nextPage, pageSize.value)
}
}
const loadMore = () => {
const nextPage = Math.ceil(props.data.length / pageSize.value) + 1
emit('load-more', nextPage, pageSize.value)
}
watch(
() => [props.data.length, totalRecords.value, pageSize.value],
() => {
if (currentPage.value > availablePages.value) {
currentPage.value = availablePages.value
}
}
)
</script>
<template>
@@ -192,7 +251,7 @@ const handleAction = (actionId: string) => {
</TableRow>
<TableRow
v-else
v-for="row in data"
v-for="row in paginatedData"
:key="row.id"
class="cursor-pointer hover:bg-muted/50"
@click="emit('row-click', row)"
@@ -227,7 +286,26 @@ const handleAction = (actionId: string) => {
</Table>
</div>
<!-- Pagination would go here -->
<div v-if="showPagination" class="flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground">
<div class="flex items-center gap-2">
<span>Showing {{ pageStart }}-{{ pageEnd }} of {{ totalRecords }} records</span>
<span v-if="showLoadMore">
(loaded {{ data.length }})
</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" :disabled="!canGoPrev" @click="goToPage(currentPage - 1)">
Previous
</Button>
<span class="px-2">Page {{ currentPage }} of {{ totalPages }}</span>
<Button variant="outline" size="sm" :disabled="!canGoNext" @click="goToPage(currentPage + 1)">
Next
</Button>
<Button v-if="showLoadMore" variant="secondary" size="sm" @click="loadMore">
Load more
</Button>
</div>
</div>
</div>
</template>