diff --git a/backend/api/account/client/views.py b/backend/api/account/client/views.py index 27e8500..aa0a3fa 100644 --- a/backend/api/account/client/views.py +++ b/backend/api/account/client/views.py @@ -233,7 +233,7 @@ class LeadViewSet(ViewSet): if is_valid: try: - lead = serializer.save() + lead: Leads = serializer.save() # собираем ответ с данными о заявке для фронта response_data = { @@ -247,16 +247,31 @@ class LeadViewSet(ViewSet): } } - # Тестовая отправка email + # отправляем емаил владельцу маршрута try: + route_owner_email = route.owner.email + email_subject = f"Новая заявка на перевозку #{lead.id}" + email_content = f""" +

Получена новая заявка на перевозку

+

Детали заявки:

+ + """ + email_result = send_email( - to=["timofey.syr1704@yandex.by"], - subject="Тестовое письмо от TripWB", - html_content="

Тест отправки письма

Если вы видите это сообщение, значит отправка работает!

" + to=[route_owner_email], + subject=email_subject, + html_content=email_content ) - print(f"Email sending result: {email_result}") + + if "Ошибка" in email_result: + print(f"Warning: Failed to send email notification: {email_result}") except Exception as email_error: - print(f"Error sending email: {str(email_error)}") + print(f"Warning: Error while sending email notification: {str(email_error)}") return Response(response_data, status=status.HTTP_201_CREATED) except Exception as e: diff --git a/frontend/app/(urls)/search/[category]/page.tsx b/frontend/app/(urls)/search/[category]/page.tsx index 57b27b4..7ed8172 100644 --- a/frontend/app/(urls)/search/[category]/page.tsx +++ b/frontend/app/(urls)/search/[category]/page.tsx @@ -51,7 +51,7 @@ export async function generateMetadata(): Promise { 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 (
@@ -63,7 +63,12 @@ export default async function SearchPage(props: SearchPageProps) { Загрузка результатов...
}> - + ) diff --git a/frontend/app/(urls)/search/components/ClientResults.tsx b/frontend/app/(urls)/search/components/ClientResults.tsx index c554b92..2aab85f 100644 --- a/frontend/app/(urls)/search/components/ClientResults.tsx +++ b/frontend/app/(urls)/search/components/ClientResults.tsx @@ -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([]) const [selectedPackageTypes, setSelectedPackageTypes] = useState([]) + 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 ( - <> +
-
- {filteredResults.length > 0 ? ( - filteredResults.map((item: SearchCardProps) => ) - ) : ( -
- По выбранным фильтрам ничего не найдено. Попробуйте изменить параметры поиска. + {isLoading ? ( +
+
+
+ ) : ( + <> +
+ {filteredResults.map(result => ( + + ))}
- )} -
- + + + + {filteredResults.length === 0 && ( +
Результаты не найдены
+ )} + + )} +
) } diff --git a/frontend/app/(urls)/search/components/Pagination.tsx b/frontend/app/(urls)/search/components/Pagination.tsx new file mode 100644 index 0000000..895c4b3 --- /dev/null +++ b/frontend/app/(urls)/search/components/Pagination.tsx @@ -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) => ( + + ) + + // всегда показываем первую страницу + pages.push(createPageButton(1)) + + if (totalPages <= maxVisiblePages) { + // если страниц мало, показываем все + for (let i = 2; i <= totalPages; i++) { + pages.push(createPageButton(i)) + } + } else { + // если страниц много, показываем с многоточием + if (currentPage > 3) { + pages.push( + + ... + + ) + } + + // показываем страницы вокруг текущей + 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( + + ... + + ) + } + + // всегда показываем последнюю страницу + if (totalPages > 1) { + pages.push(createPageButton(totalPages)) + } + } + + return pages + } + + return ( +
+
{renderPageNumbers()}
+
+ ) +} diff --git a/frontend/app/types/index.ts b/frontend/app/types/index.ts index b3f94b6..47686ea 100644 --- a/frontend/app/types/index.ts +++ b/frontend/app/types/index.ts @@ -235,6 +235,8 @@ export interface PricingCardProps { export interface SearchResponse { count: number results: SearchCardProps[] + next: string | null + previous: string | null } export interface SearchPageProps { diff --git a/frontend/components/forms/RouteForm.tsx b/frontend/components/forms/RouteForm.tsx index 18b212c..0f6c733 100644 --- a/frontend/components/forms/RouteForm.tsx +++ b/frontend/components/forms/RouteForm.tsx @@ -80,7 +80,7 @@ const RouteForm: React.FC = ({ arrival: '', phone_number: user?.phone_number || '', comment: '', - email_notification: false, + email_notification: true, } const cargoOptions: SelectOption[] = cargo_types.map((type, index) => ({ diff --git a/frontend/lib/search/fetchRoutes.ts b/frontend/lib/search/fetchRoutes.ts index 903f60e..eedca21 100644 --- a/frontend/lib/search/fetchRoutes.ts +++ b/frontend/lib/search/fetchRoutes.ts @@ -1,10 +1,15 @@ import { SearchResponse } from '@/app/types' // получаем все предложения по выбранному owner_type -export async function fetchRoutes(category: string, query: string = ''): Promise { +export async function fetchRoutes( + category: string, + query: string = '', + page: number = 1 +): Promise { try { + const pageParam = page > 1 ? `${query ? '&' : '?'}page=${page}` : '' const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/search/${category}/${query ? `?${query}` : ''}`, + `${process.env.NEXT_PUBLIC_API_URL}/search/${category}/${query}${pageParam}`, { cache: 'no-store', } @@ -15,9 +20,14 @@ export async function fetchRoutes(category: string, query: string = ''): Promise } const data = await response.json() - return data + return { + results: data.results, + count: data.count, + next: data.next, + previous: data.previous, + } } catch (error) { console.error('Error fetching search results:', error) - return { results: [], count: 0 } + return { results: [], count: 0, next: null, previous: null } } }