120 lines
3.4 KiB
Vue
120 lines
3.4 KiB
Vue
<template>
|
||
<div class="m-5">
|
||
<h1 class="text-2xl font-bold mb-4">Replays</h1>
|
||
|
||
<table class="w-full border text-left">
|
||
<thead>
|
||
<tr class="bg-gray-100">
|
||
<th class="p-2">ID</th>
|
||
<th class="p-2">Email</th>
|
||
<th class="p-2">Date</th>
|
||
<th class="p-2">Location</th>
|
||
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="replay in data.replays" :key="replay.id" class="border-t">
|
||
<td class="p-2">{{ replay.id }}</td>
|
||
<td class="p-2">{{ replay.user.email ?? replay.user.display_name }}</td>
|
||
<td class="p-2">{{ replay.finished_at }}</td>
|
||
<td class="p-2">{{ replay.user.geo ? `${replay.user.geo.city} ${replay.user.geo.subdivision}, ${replay.user.geo.region}` : 'unknown' }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination -->
|
||
<div class="mt-4 flex flex-wrap items-center gap-2" v-if="data.meta && data.meta.links && data.meta.links.length > 1">
|
||
<!-- First -->
|
||
<Link
|
||
v-if="firstPageUrl && !isFirstPage"
|
||
:href="firstPageUrl"
|
||
class="px-3 py-1 border rounded text-sm"
|
||
>
|
||
« First
|
||
</Link>
|
||
|
||
<!-- Previous -->
|
||
<Link
|
||
v-if="prevPageUrl"
|
||
:href="prevPageUrl"
|
||
class="px-3 py-1 border rounded text-sm"
|
||
>
|
||
‹ Prev
|
||
</Link>
|
||
|
||
<!-- Page Numbers (windowed) -->
|
||
<template v-for="link in paginatedLinks" :key="link.label">
|
||
<component
|
||
:is="link.url ? Link : 'span'"
|
||
:href="link.url"
|
||
class="px-3 py-1 border rounded text-sm"
|
||
:class="{
|
||
'font-bold bg-gray-300': link.active,
|
||
'text-gray-400 cursor-not-allowed': !link.url
|
||
}"
|
||
>
|
||
<span v-html="link.label" />
|
||
</component>
|
||
</template>
|
||
|
||
<!-- Next -->
|
||
<Link
|
||
v-if="nextPageUrl"
|
||
:href="nextPageUrl"
|
||
class="px-3 py-1 border rounded text-sm"
|
||
>
|
||
Next ›
|
||
</Link>
|
||
|
||
<!-- Last -->
|
||
<Link
|
||
v-if="lastPageUrl && !isLastPage"
|
||
:href="lastPageUrl"
|
||
class="px-3 py-1 border rounded text-sm"
|
||
>
|
||
Last »
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
import { Link } from '@inertiajs/vue3'
|
||
|
||
const props = defineProps({
|
||
data: Object
|
||
})
|
||
|
||
// Core pagination values
|
||
const links = computed(() => props.data.meta.links || [])
|
||
const currentIndex = computed(() => links.value.findIndex(link => link.active))
|
||
|
||
const maxVisible = 10
|
||
const half = Math.floor(maxVisible / 2)
|
||
|
||
const paginatedLinks = computed(() => {
|
||
const total = links.value.length
|
||
if (total <= maxVisible) return links.value
|
||
|
||
let start = Math.max(currentIndex.value - half, 0)
|
||
let end = start + maxVisible
|
||
|
||
if (end > total) {
|
||
end = total
|
||
start = Math.max(0, end - maxVisible)
|
||
}
|
||
|
||
return links.value.slice(start, end)
|
||
})
|
||
|
||
// Navigation links
|
||
const firstPageUrl = computed(() => links.value[1]?.url) // usually index 1 is page=1
|
||
const prevPageUrl = computed(() => links.value[currentIndex.value - 1]?.url)
|
||
const nextPageUrl = computed(() => links.value[currentIndex.value + 1]?.url)
|
||
const lastPageUrl = computed(() => links.value[links.value.length - 2]?.url) // last item is "Next »", second-last is last numbered
|
||
|
||
const isFirstPage = computed(() => links.value[currentIndex.value]?.label === '1')
|
||
const isLastPage = computed(() => links.value[currentIndex.value]?.label === props.data.meta.last_page)
|
||
</script>
|