<script setup lang="ts">
import { computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
// Кнопочная пагинация: ‹ 1 … p-1 [p] p+1 … N › (по 30 на страницу).
// Клиентская нарезка: родитель передаёт total и v-model:page, элементы
// списка режет computed — списки и так грузятся целиком (фильтры и
// счётчики зависят от полного набора).
const props = withDefaults(
defineProps<{ page: number; total: number; perPage?: number }>(),
{ perPage: 30 },
)
const emit = defineEmits<{ 'update:page': [page: number] }>()
const { t } = useI18n()
const pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.perPage)))
// данные поредели (удаление, смена фильтра) — не остаёмся за последней страницей
watch(pageCount, (n) => {
if (props.page > n) emit('update:page', n)
})
// номера страниц с усечением: края + соседние с текущей, между ними «…»
const pages = computed<(number | '…')[]>(() => {
const n = pageCount.value
const cur = props.page
if (n <= 7) return Array.from({ length: n }, (_, i) => i + 1)
const keep = new Set([1, n, cur - 1, cur, cur + 1])
const out: (number | '…')[] = []
let prev = 0
for (let i = 1; i <= n; i++) {
if (!keep.has(i)) continue
if (prev && i - prev > 1) out.push('…')
out.push(i)
prev = i
}
return out
})
function go(p: number): void {
if (p >= 1 && p <= pageCount.value && p !== props.page) emit('update:page', p)
}
</script>
<template>
<nav v-if="pageCount > 1" class="pagination" :aria-label="t('pagination.aria')">
<GnIconButton
icon="ph-caret-left"
:label="t('pagination.prev')"
:disabled="page <= 1"
@click="go(page - 1)"
/>
<template v-for="(p, i) in pages" :key="`${p}-${i}`">
<span v-if="p === '…'" class="dots">…</span>
<GnButton
v-else
size="sm"
:variant="p === page ? 'primary' : 'secondary'"
@click="go(p)"
>
{{ p }}
</GnButton>
</template>
<GnIconButton
icon="ph-caret-right"
:label="t('pagination.next')"
:disabled="page >= pageCount"
@click="go(page + 1)"
/>
<span class="page-info">{{ t('pagination.pageOf', { n: page, m: pageCount }) }}</span>
</nav>
</template>
<style scoped>
.pagination {
display: flex;
gap: 0.35rem;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 1.25rem;
}
.dots {
color: var(--text-muted, #888);
padding: 0 0.2rem;
}
.page-info {
color: var(--text-muted, #888);
font-size: 0.85em;
margin-left: 0.5rem;
}
</style>