implement pagination
This commit is contained in:
@@ -51,7 +51,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
|
||||
export default async function SearchPage(props: SearchPageProps) {
|
||||
const params = await props.params
|
||||
const { results, count } = await fetchRoutes(params.category || '')
|
||||
const { results, count, next, previous } = await fetchRoutes(params.category || '')
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
@@ -63,7 +63,12 @@ export default async function SearchPage(props: SearchPageProps) {
|
||||
<AddressSelector is_search={true} />
|
||||
|
||||
<Suspense fallback={<div>Загрузка результатов...</div>}>
|
||||
<ClientResults initialResults={results} />
|
||||
<ClientResults
|
||||
initialResults={results}
|
||||
initialCount={count}
|
||||
initialNext={next}
|
||||
initialPrevious={previous}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,20 +4,40 @@ import React, { useState, useMemo } from 'react'
|
||||
import SearchCard from './SearchCard'
|
||||
import { SearchCardProps } from '@/app/types'
|
||||
import SearchFilters from './SearchFilters'
|
||||
import { fetchRoutes } from '@/lib/search/fetchRoutes'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import Pagination from './Pagination'
|
||||
|
||||
interface ClientResultsProps {
|
||||
initialResults: SearchCardProps[]
|
||||
initialCount?: number
|
||||
initialNext?: string | null
|
||||
initialPrevious?: string | null
|
||||
}
|
||||
|
||||
export default function ClientResults({ initialResults }: ClientResultsProps) {
|
||||
export default function ClientResults({
|
||||
initialResults,
|
||||
initialCount = 0,
|
||||
initialNext = null,
|
||||
initialPrevious = null,
|
||||
}: ClientResultsProps) {
|
||||
const [selectedTransport, setSelectedTransport] = useState<number[]>([])
|
||||
const [selectedPackageTypes, setSelectedPackageTypes] = useState<number[]>([])
|
||||
const [results, setResults] = useState(initialResults)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [hasNext, setHasNext] = useState(!!initialNext)
|
||||
const [hasPrevious, setHasPrevious] = useState(!!initialPrevious)
|
||||
const [totalCount, setTotalCount] = useState(initialCount)
|
||||
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const filteredResults = useMemo(() => {
|
||||
let results = [...initialResults]
|
||||
let filtered = [...results]
|
||||
|
||||
if (selectedTransport.length > 0) {
|
||||
results = results.filter(item =>
|
||||
filtered = filtered.filter(item =>
|
||||
selectedTransport.some(id => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
@@ -34,7 +54,7 @@ export default function ClientResults({ initialResults }: ClientResultsProps) {
|
||||
}
|
||||
|
||||
if (selectedPackageTypes.length > 0) {
|
||||
results = results.filter(item => {
|
||||
filtered = filtered.filter(item => {
|
||||
return selectedPackageTypes.some(id => {
|
||||
const match = (() => {
|
||||
switch (id) {
|
||||
@@ -58,8 +78,8 @@ export default function ClientResults({ initialResults }: ClientResultsProps) {
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}, [initialResults, selectedTransport, selectedPackageTypes])
|
||||
return filtered
|
||||
}, [results, selectedTransport, selectedPackageTypes])
|
||||
|
||||
const handleFiltersChange = ({
|
||||
transport,
|
||||
@@ -72,19 +92,61 @@ export default function ClientResults({ initialResults }: ClientResultsProps) {
|
||||
setSelectedPackageTypes(packageTypes)
|
||||
}
|
||||
|
||||
const handlePageChange = async (newPage: number) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const category = pathname.split('/')[2] // получаем категорию из URL
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
const {
|
||||
results: newResults,
|
||||
next,
|
||||
previous,
|
||||
count,
|
||||
} = await fetchRoutes(category, params.toString(), newPage)
|
||||
setResults(newResults)
|
||||
setCurrentPage(newPage)
|
||||
setHasNext(!!next)
|
||||
setHasPrevious(!!previous)
|
||||
setTotalCount(count)
|
||||
} catch (error) {
|
||||
console.error('Error fetching page:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(totalCount / 10) // 10 - размер страницы
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-6">
|
||||
<SearchFilters onFiltersChange={handleFiltersChange} />
|
||||
|
||||
<div className="space-y-4">
|
||||
{filteredResults.length > 0 ? (
|
||||
filteredResults.map((item: SearchCardProps) => <SearchCard key={item.id} {...item} />)
|
||||
) : (
|
||||
<div className="rounded-lg bg-orange-50 p-4 text-center text-orange-800">
|
||||
По выбранным фильтрам ничего не найдено. Попробуйте изменить параметры поиска.
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-gray-900"></div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-4">
|
||||
{filteredResults.map(result => (
|
||||
<SearchCard key={result.id} {...result} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
hasNext={hasNext}
|
||||
hasPrevious={hasPrevious}
|
||||
isLoading={isLoading}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
|
||||
{filteredResults.length === 0 && (
|
||||
<div className="text-center text-gray-500">Результаты не найдены</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
91
frontend/app/(urls)/search/components/Pagination.tsx
Normal file
91
frontend/app/(urls)/search/components/Pagination.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface PaginationProps {
|
||||
currentPage: number
|
||||
totalPages: number
|
||||
hasNext: boolean
|
||||
hasPrevious: boolean
|
||||
isLoading: boolean
|
||||
onPageChange: (page: number) => void
|
||||
}
|
||||
|
||||
export default function Pagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
isLoading,
|
||||
onPageChange,
|
||||
}: PaginationProps) {
|
||||
if (totalPages <= 1) return null
|
||||
|
||||
const renderPageNumbers = () => {
|
||||
const pages = []
|
||||
const maxVisiblePages = 5 // максимальное количество видимых страниц
|
||||
|
||||
// создаем кнопки страницы
|
||||
const createPageButton = (pageNum: number) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => onPageChange(pageNum)}
|
||||
disabled={isLoading || pageNum === currentPage}
|
||||
className={`h-8 w-8 rounded-full ${
|
||||
pageNum === currentPage
|
||||
? 'bg-orange/80 text-white'
|
||||
: 'bg-white text-gray-700 hover:bg-gray-100'
|
||||
} flex items-center justify-center border text-sm font-medium transition-colors`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
)
|
||||
|
||||
// всегда показываем первую страницу
|
||||
pages.push(createPageButton(1))
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
// если страниц мало, показываем все
|
||||
for (let i = 2; i <= totalPages; i++) {
|
||||
pages.push(createPageButton(i))
|
||||
}
|
||||
} else {
|
||||
// если страниц много, показываем с многоточием
|
||||
if (currentPage > 3) {
|
||||
pages.push(
|
||||
<span key="start-ellipsis" className="px-2">
|
||||
...
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// показываем страницы вокруг текущей
|
||||
for (
|
||||
let i = Math.max(2, currentPage - 1);
|
||||
i <= Math.min(totalPages - 1, currentPage + 1);
|
||||
i++
|
||||
) {
|
||||
pages.push(createPageButton(i))
|
||||
}
|
||||
|
||||
if (currentPage < totalPages - 2) {
|
||||
pages.push(
|
||||
<span key="end-ellipsis" className="px-2">
|
||||
...
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// всегда показываем последнюю страницу
|
||||
if (totalPages > 1) {
|
||||
pages.push(createPageButton(totalPages))
|
||||
}
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 flex items-center justify-center">
|
||||
<div className="flex space-x-2">{renderPageNumbers()}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -235,6 +235,8 @@ export interface PricingCardProps {
|
||||
export interface SearchResponse {
|
||||
count: number
|
||||
results: SearchCardProps[]
|
||||
next: string | null
|
||||
previous: string | null
|
||||
}
|
||||
|
||||
export interface SearchPageProps {
|
||||
|
||||
Reference in New Issue
Block a user