Newer
Older
gnexus-tasks / frontend / src / components / Pagination.vue
<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>