feat: Implement Load More pagination for matches page
- Add pagination state management (matches, hasMore, nextPageTime) - Create loadMore() function to fetch and append next page of results - Replace placeholder "pagination coming soon" with functional Load More button - Add loading spinner during pagination requests - Show total matches count and "all loaded" message when complete - Use $effect to reset pagination state when filters change Completes Phase 5.2 pagination requirement from TODO.md. Users can now browse through large match lists efficiently. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Search, Filter, Calendar } from 'lucide-svelte';
|
||||
import { Search, Filter, Calendar, Loader2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { api } from '$lib/api';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import Card from '$lib/components/ui/Card.svelte';
|
||||
import Badge from '$lib/components/ui/Badge.svelte';
|
||||
import MatchCard from '$lib/components/match/MatchCard.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import type { MatchListItem } from '$lib/types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -18,6 +20,19 @@
|
||||
let searchQuery = $state(currentSearch);
|
||||
let showFilters = $state(false);
|
||||
|
||||
// Pagination state
|
||||
let matches = $state<MatchListItem[]>(data.matches);
|
||||
let hasMore = $state(data.hasMore);
|
||||
let nextPageTime = $state(data.nextPageTime);
|
||||
let isLoadingMore = $state(false);
|
||||
|
||||
// Reset pagination when data changes (new filters applied)
|
||||
$effect(() => {
|
||||
matches = data.matches;
|
||||
hasMore = data.hasMore;
|
||||
nextPageTime = data.nextPageTime;
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
const params = new URLSearchParams();
|
||||
if (searchQuery) params.set('search', searchQuery);
|
||||
@@ -27,7 +42,39 @@
|
||||
goto(`/matches?${params.toString()}`);
|
||||
};
|
||||
|
||||
const commonMaps = ['de_dust2', 'de_mirage', 'de_inferno', 'de_nuke', 'de_overpass', 'de_ancient', 'de_anubis'];
|
||||
const loadMore = async () => {
|
||||
if (!hasMore || isLoadingMore || !nextPageTime) return;
|
||||
|
||||
isLoadingMore = true;
|
||||
try {
|
||||
const matchesData = await api.matches.getMatches({
|
||||
limit: 50,
|
||||
map: data.filters.map,
|
||||
player_id: data.filters.playerId,
|
||||
before_time: nextPageTime
|
||||
});
|
||||
|
||||
// Append new matches to existing list
|
||||
matches = [...matches, ...matchesData.matches];
|
||||
hasMore = matchesData.has_more;
|
||||
nextPageTime = matchesData.next_page_time;
|
||||
} catch (error) {
|
||||
console.error('Failed to load more matches:', error);
|
||||
// Show error toast or message here
|
||||
} finally {
|
||||
isLoadingMore = false;
|
||||
}
|
||||
};
|
||||
|
||||
const commonMaps = [
|
||||
'de_dust2',
|
||||
'de_mirage',
|
||||
'de_inferno',
|
||||
'de_nuke',
|
||||
'de_overpass',
|
||||
'de_ancient',
|
||||
'de_anubis'
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -42,7 +89,13 @@
|
||||
|
||||
<!-- Search & Filters -->
|
||||
<Card padding="lg" class="mb-8">
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSearch(); }} class="flex flex-col gap-4">
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSearch();
|
||||
}}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
<div class="flex flex-col gap-4 md:flex-row">
|
||||
<div class="flex-1">
|
||||
<div class="relative">
|
||||
@@ -73,7 +126,7 @@
|
||||
{#each commonMaps as mapName}
|
||||
<a
|
||||
href={`/matches?map=${mapName}`}
|
||||
class="badge badge-lg badge-outline hover:badge-primary"
|
||||
class="badge badge-outline badge-lg hover:badge-primary"
|
||||
class:badge-primary={currentMap === mapName}
|
||||
>
|
||||
{mapName}
|
||||
@@ -103,16 +156,31 @@
|
||||
</Card>
|
||||
|
||||
<!-- Matches Grid -->
|
||||
{#if data.matches.length > 0}
|
||||
{#if matches.length > 0}
|
||||
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{#each data.matches as match}
|
||||
{#each matches as match}
|
||||
<MatchCard {match} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if data.hasMore}
|
||||
<!-- Load More Button -->
|
||||
{#if hasMore}
|
||||
<div class="mt-8 text-center">
|
||||
<Badge variant="info">More matches available - pagination coming soon</Badge>
|
||||
<Button variant="primary" size="lg" onclick={loadMore} disabled={isLoadingMore}>
|
||||
{#if isLoadingMore}
|
||||
<Loader2 class="mr-2 h-5 w-5 animate-spin" />
|
||||
Loading...
|
||||
{:else}
|
||||
Load More Matches
|
||||
{/if}
|
||||
</Button>
|
||||
<p class="mt-2 text-sm text-base-content/60">
|
||||
Showing {matches.length} matches
|
||||
</p>
|
||||
</div>
|
||||
{:else if matches.length > 0}
|
||||
<div class="mt-8 text-center">
|
||||
<Badge variant="default">All matches loaded ({matches.length} total)</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
@@ -120,9 +188,7 @@
|
||||
<div class="text-center">
|
||||
<Calendar class="mx-auto mb-4 h-16 w-16 text-base-content/40" />
|
||||
<h2 class="mb-2 text-xl font-semibold text-base-content">No Matches Found</h2>
|
||||
<p class="text-base-content/60">
|
||||
Try adjusting your filters or search query.
|
||||
</p>
|
||||
<p class="text-base-content/60">Try adjusting your filters or search query.</p>
|
||||
{#if currentMap || currentPlayerId || currentSearch}
|
||||
<div class="mt-4">
|
||||
<Button variant="primary" href="/matches">View All Matches</Button>
|
||||
|
||||
Reference in New Issue
Block a user