feat: persist pagination
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { FC, ReactElement, useState, useEffect } from 'react';
|
import { FC, ReactElement, useState, useEffect, useCallback } from 'react';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { Link, useNavigate } from 'react-router';
|
import { Link, useNavigate, useSearchParams } from 'react-router';
|
||||||
import {
|
import {
|
||||||
useTeams,
|
useTeams,
|
||||||
useJoinTeam,
|
useJoinTeam,
|
||||||
@@ -44,26 +44,94 @@ const TeamCardSkeleton: FC = () => (
|
|||||||
|
|
||||||
const BrowseTeamsPage: FC = (): ReactElement => {
|
const BrowseTeamsPage: FC = (): ReactElement => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
||||||
const [selectedCity, setSelectedCity] = useState('');
|
// Initialize state from URL params
|
||||||
|
const initialPage = parseInt(searchParams.get('page') || '1', 10);
|
||||||
|
const initialPerPage = parseInt(
|
||||||
|
searchParams.get('per_page') || String(DEFAULT_PER_PAGE),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
const initialSearch = searchParams.get('search') || '';
|
||||||
|
const initialCity = searchParams.get('city') || '';
|
||||||
|
|
||||||
|
const [searchTerm, setSearchTerm] = useState(initialSearch);
|
||||||
|
const [debouncedSearch, setDebouncedSearch] = useState(initialSearch);
|
||||||
|
const [selectedCity, setSelectedCity] = useState(initialCity);
|
||||||
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
|
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
|
||||||
const [showJoinModal, setShowJoinModal] = useState(false);
|
const [showJoinModal, setShowJoinModal] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(
|
||||||
const [perPage, setPerPage] = useState(DEFAULT_PER_PAGE);
|
PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPage : 1
|
||||||
|
);
|
||||||
|
const [perPage, setPerPage] = useState(
|
||||||
|
PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPerPage : DEFAULT_PER_PAGE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update URL when pagination state changes
|
||||||
|
const updateUrlParams = useCallback(
|
||||||
|
(params: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
search?: string;
|
||||||
|
city?: string;
|
||||||
|
}) => {
|
||||||
|
const newParams = new URLSearchParams(searchParams);
|
||||||
|
|
||||||
|
if (params.page !== undefined) {
|
||||||
|
if (params.page === 1) {
|
||||||
|
newParams.delete('page');
|
||||||
|
} else {
|
||||||
|
newParams.set('page', String(params.page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.per_page !== undefined) {
|
||||||
|
if (params.per_page === DEFAULT_PER_PAGE) {
|
||||||
|
newParams.delete('per_page');
|
||||||
|
} else {
|
||||||
|
newParams.set('per_page', String(params.per_page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.search !== undefined) {
|
||||||
|
if (params.search === '') {
|
||||||
|
newParams.delete('search');
|
||||||
|
} else {
|
||||||
|
newParams.set('search', params.search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.city !== undefined) {
|
||||||
|
if (params.city === '') {
|
||||||
|
newParams.delete('city');
|
||||||
|
} else {
|
||||||
|
newParams.set('city', params.city);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams(newParams, { replace: true });
|
||||||
|
},
|
||||||
|
[searchParams, setSearchParams]
|
||||||
|
);
|
||||||
|
|
||||||
// Debounce search term and reset page
|
// Debounce search term and reset page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setDebouncedSearch(searchTerm);
|
setDebouncedSearch(searchTerm);
|
||||||
setCurrentPage(1); // Reset to first page on search
|
if (searchTerm !== debouncedSearch) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
updateUrlParams({ search: searchTerm, page: 1 });
|
||||||
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
// Reset page when city filter changes
|
// Update URL when city filter changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage(1);
|
if (selectedCity !== initialCity) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
updateUrlParams({ city: selectedCity, page: 1 });
|
||||||
|
}
|
||||||
}, [selectedCity]);
|
}, [selectedCity]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -329,7 +397,11 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
onClick={() => {
|
||||||
|
const newPage = Math.max(1, currentPage - 1);
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
updateUrlParams({ page: newPage });
|
||||||
|
}}
|
||||||
disabled={currentPage === 1 || isFetching}
|
disabled={currentPage === 1 || isFetching}
|
||||||
className="px-3"
|
className="px-3"
|
||||||
>
|
>
|
||||||
@@ -348,7 +420,10 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
key={page}
|
key={page}
|
||||||
onClick={() => setCurrentPage(page)}
|
onClick={() => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
updateUrlParams({ page });
|
||||||
|
}}
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
className={`min-w-10 h-10 px-3 rounded-md text-sm font-medium transition-colors ${
|
className={`min-w-10 h-10 px-3 rounded-md text-sm font-medium transition-colors ${
|
||||||
currentPage === page
|
currentPage === page
|
||||||
@@ -364,7 +439,11 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
onClick={() => {
|
||||||
|
const newPage = Math.min(totalPages, currentPage + 1);
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
updateUrlParams({ page: newPage });
|
||||||
|
}}
|
||||||
disabled={currentPage === totalPages || isFetching}
|
disabled={currentPage === totalPages || isFetching}
|
||||||
className="px-3"
|
className="px-3"
|
||||||
>
|
>
|
||||||
@@ -385,8 +464,10 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
<select
|
<select
|
||||||
value={perPage}
|
value={perPage}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPerPage(Number(e.target.value));
|
const newPerPage = Number(e.target.value);
|
||||||
|
setPerPage(newPerPage);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
|
updateUrlParams({ per_page: newPerPage, page: 1 });
|
||||||
}}
|
}}
|
||||||
className="h-10 px-3 pr-8 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="h-10 px-3 pr-8 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user