Teren-app/resources/js/Components/DataTable/DataTablePaginationClient.vue
2026-01-02 12:32:20 +01:00

206 lines
6.5 KiB
Vue

<script setup>
import { ref, computed } from "vue";
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-vue-next";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationFirst,
PaginationItem,
PaginationLast,
PaginationNext,
PaginationPrevious,
} from "@/Components/ui/pagination";
import { Separator } from "@/Components/ui/separator";
const props = defineProps({
currentPage: { type: Number, required: true },
lastPage: { type: Number, required: true },
total: { type: Number, required: true },
showingFrom: { type: Number, required: true },
showingTo: { type: Number, required: true },
showPageStats: { type: Boolean, default: true },
showGoto: { type: Boolean, default: true },
maxPageLinks: { type: Number, default: 5 },
perPage: { type: Number, default: 10 },
});
const emit = defineEmits(["update:page"]);
const gotoInput = ref("");
function goToPageInput() {
const raw = String(gotoInput.value || "").trim();
const n = Number(raw);
if (!Number.isFinite(n)) return;
const target = Math.max(1, Math.min(props.lastPage, Math.floor(n)));
if (target !== props.currentPage) setPage(target);
gotoInput.value = "";
}
const visiblePages = computed(() => {
const pages = [];
const count = props.lastPage;
if (count <= 1) return [1];
const windowSize = Math.max(3, props.maxPageLinks);
const half = Math.floor(windowSize / 2);
let start = Math.max(1, props.currentPage - half);
let end = Math.min(count, start + windowSize - 1);
start = Math.max(1, Math.min(start, end - windowSize + 1));
// Handle first page
if (start > 1) {
pages.push(1);
if (start > 2) pages.push("...");
}
// Add pages in window
for (let i = start; i <= end; i++) {
pages.push(i);
}
// Handle last page
if (end < count) {
if (end < count - 1) pages.push("...");
pages.push(count);
}
return pages;
});
function setPage(p) {
emit("update:page", Math.min(Math.max(1, p), props.lastPage));
}
</script>
<template>
<nav
class="flex flex-wrap items-center justify-between gap-3 px-2 text-sm text-gray-700 sm:px-1"
aria-label="Pagination"
>
<!-- Mobile: Simple prev/next -->
<div class="flex flex-1 justify-between sm:hidden">
<button
@click="setPage(currentPage - 1)"
:disabled="currentPage <= 1"
class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Prejšnja
</button>
<button
@click="setPage(currentPage + 1)"
:disabled="currentPage >= lastPage"
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Naslednja
</button>
</div>
<!-- Desktop: Full pagination -->
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<!-- Page stats with modern badge style -->
<div v-if="showPageStats">
<div v-if="total > 0" class="flex items-center gap-2">
<span class="text-sm text-muted-foreground">Prikazano</span>
<div
class="inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-sm font-medium"
>
<span class="text-foreground">{{ showingFrom }}</span>
<span class="text-muted-foreground">-</span>
<span class="text-foreground">{{ showingTo }}</span>
</div>
<span class="text-sm text-muted-foreground">od</span>
<div
class="inline-flex items-center rounded-md bg-primary/10 px-2.5 py-1 text-sm font-semibold text-primary"
>
{{ total }}
</div>
</div>
<div
v-else
class="inline-flex items-center gap-2 rounded-lg bg-muted/50 px-3 py-1.5"
>
<span class="text-sm font-medium text-muted-foreground">Ni zadetkov</span>
</div>
</div>
<!-- Pagination controls -->
<Pagination
v-slot="{ page }"
:total="total"
:items-per-page="perPage"
:sibling-count="1"
show-edges
:default-page="currentPage"
:page="currentPage"
>
<PaginationContent>
<!-- First -->
<PaginationFirst :disabled="currentPage <= 1" @click="setPage(1)">
<ChevronsLeft />
</PaginationFirst>
<!-- Previous -->
<PaginationPrevious
:disabled="currentPage <= 1"
@click="setPage(currentPage - 1)"
>
<ChevronLeft />
</PaginationPrevious>
<!-- Page numbers -->
<template v-for="(item, index) in visiblePages" :key="index">
<PaginationEllipsis v-if="item === '...'" />
<PaginationItem
v-else
:value="item"
:is-active="currentPage === item"
@click="setPage(item)"
>
{{ item }}
</PaginationItem>
</template>
<!-- Next -->
<PaginationNext
:disabled="currentPage >= lastPage"
@click="setPage(currentPage + 1)"
>
<ChevronRight />
</PaginationNext>
<!-- Last -->
<PaginationLast
:disabled="currentPage >= lastPage"
@click="setPage(lastPage)"
>
<ChevronsRight />
</PaginationLast>
</PaginationContent>
</Pagination>
<!-- Goto page input -->
<div v-if="showGoto" class="flex items-center gap-3">
<div
class="inline-flex items-center gap-2 rounded-md border border-input bg-background px-2 h-8"
>
<input
v-model="gotoInput"
type="number"
min="1"
:max="lastPage"
inputmode="numeric"
class="w-10 h-full text-sm text-center bg-transparent border-0 outline-none focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
:placeholder="String(currentPage)"
aria-label="Pojdi na stran"
@keyup.enter="goToPageInput"
@blur="goToPageInput"
/>
<Separator orientation="vertical" class="h-full" />
<span class="text-sm text-muted-foreground">{{ lastPage }}</span>
</div>
</div>
</div>
</nav>
</template>