search page without from-to

This commit is contained in:
2025-05-23 14:49:12 +03:00
parent f3ca53d907
commit fa70a96c27
9 changed files with 142 additions and 24 deletions

View File

View File

@@ -0,0 +1,10 @@
from api.main.serializers import HomePageRouteSerializer
from routes.models import Route
class SearchRouteSerializer(HomePageRouteSerializer):
class Meta(HomePageRouteSerializer.Meta):
model = Route
fields = HomePageRouteSerializer.Meta.fields

View File

@@ -0,0 +1,24 @@
from rest_framework import generics
from routes.models import Route
from .serializers import SearchRouteSerializer
from api.utils.pagination import StandardResultsSetPagination
from rest_framework.exceptions import ValidationError
from routes.constants.routeChoices import owner_type_choices
class SearchRouteListView(generics.ListAPIView):
serializer_class = SearchRouteSerializer
pagination_class = StandardResultsSetPagination
def get_queryset(self):
owner_type = self.kwargs.get('owner_type')
valid_types = [choice[0] for choice in owner_type_choices]
if not owner_type or owner_type not in valid_types:
raise ValidationError("Invalid or missing owner_type. Must be either 'customer' or 'mover'")
queryset = Route.objects.filter(
owner_type=owner_type
).order_by('-arrival_DT')
return queryset

View File

@@ -16,6 +16,8 @@ CountryView,
GetMembershipData,
ChangeUserMembership)
from api.search.views import SearchRouteListView
urlpatterns = [
path("v1/faq/", FAQView.as_view(), name='faqMain'),
path("v1/news/", NewsView.as_view(), name="newsmain"),
@@ -38,4 +40,6 @@ urlpatterns = [
path("v1/plans/", GetMembershipData.as_view({'get':'get_pricing_data'}), name='get_pricing_data'),
path("v1/account/change_membership/", ChangeUserMembership.as_view({'patch':'change_plan'}), name='change_plan'),
path('v1/search/<str:owner_type>/', SearchRouteListView.as_view(), name='search-routes'),
]

View File

@@ -0,0 +1,6 @@
from rest_framework.pagination import PageNumberPagination
class StandardResultsSetPagination(PageNumberPagination):
page_size = 25
page_size_query_param = 'page_size'
max_page_size = 25

View File

@@ -1,26 +1,58 @@
import React, { Suspense } from 'react'
import type { Metadata } from 'next'
import SearchCard from '../components/SearchCard'
import { SearchCardProps, SearchPageProps } from '@/app/types'
import { fetchRoutes } from '@/lib/search/fetchRoutes'
interface SearchPageProps {
params: {
category: string
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Поиск перевозчиков и посылок | Tripwb',
description:
'Найдите самые быстрые варианты по перевозке своих посылок | Tripwb - текст текст текст',
openGraph: {
title: 'Поиск текст текст | Tripwb - текст текст текст',
description: 'Найдите лучшие что то',
url: 'https://tripwb.com/search',
siteName: 'TripWB',
images: [
{
url: 'https://i.ibb.co/gmqzzmb/header-logo-mod-1200x630.png',
width: 1200,
height: 630,
alt: 'Tripwb - текст текст текст',
},
],
locale: 'ru_RU',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Поиск текст текст | TripWB',
description: 'Ттекст текст текст текст',
images: [
{
url: 'https://i.ibb.co/gmqzzmb/header-logo-mod-1200x630.png',
width: 1200,
height: 630,
alt: 'Tripwb - текст текст текст',
},
],
},
alternates: {
canonical: 'https://tripwb.com/search/',
},
robots: {
index: true,
follow: true,
},
}
searchParams: {
[key: string]: string | string[] | undefined
}
}
// получаем все предложения по выбранному owner_type
async function fetchSearch(category: string, query: string = '') {
// get search api(owner_type)
return []
}
export default async function SearchPage(props: SearchPageProps) {
const params = await props.params
const { category } = params
const initialData = await fetchSearch(category)
const { results, count } = await fetchRoutes(category)
return (
<div className="container mx-auto p-4">
@@ -29,14 +61,12 @@ export default async function SearchPage(props: SearchPageProps) {
</h1>
<Suspense fallback={<div>Загрузка результатов...</div>}>
{/* Здесь будет компонент с результатами поиска */}
<div className="space-y-4">
{initialData.map((item: any, index: number) => (
<div key={index} className="rounded-lg border p-4">
{/* Здесь будет карточка с результатом */}
<p>Результат поиска {index + 1}</p>
</div>
))}
{results.length > 0 ? (
results.map((item: SearchCardProps) => <SearchCard key={item.id} {...item} />)
) : (
<div className="text-center text-gray-500">Объявления не найдены</div>
)}
</div>
</Suspense>
</div>

View File

@@ -226,3 +226,17 @@ export interface PricingCardProps {
isActive?: boolean
onPlanChange?: () => void
}
export interface SearchResponse {
count: number
results: SearchCardProps[]
}
export interface SearchPageProps {
params: {
category: string
}
searchParams: {
[key: string]: string | string[] | undefined
}
}

View File

@@ -4,6 +4,7 @@ import React, { useEffect, useState } from 'react'
import Select from 'react-select'
import { SelectOption } from '@/app/types'
import axios from 'axios'
import Tooltip from './Tooltip'
interface LocationOption extends SelectOption {
value: string // добавляем поле value к базовому интерфейсу SelectOption
@@ -19,6 +20,7 @@ interface LocationSelectProps {
placeholder: string
countryId?: string
isCity?: boolean
tooltip?: string | React.ReactNode
}
const LocationSelect: React.FC<LocationSelectProps> = ({
@@ -26,6 +28,7 @@ const LocationSelect: React.FC<LocationSelectProps> = ({
value,
handleChange,
label,
tooltip,
placeholder,
countryId,
isCity = false,
@@ -61,9 +64,14 @@ const LocationSelect: React.FC<LocationSelectProps> = ({
return (
<div>
<label htmlFor={name} className="mb-1 block text-sm font-medium text-gray-700">
{label}
</label>
{label && (
<div className="my-2 flex items-center gap-2">
<label className="text-sm font-medium text-gray-500" htmlFor={name}>
{label}
</label>
{tooltip && <Tooltip content={tooltip} />}
</div>
)}
<Select<LocationOption>
inputId={name}
name={name}
@@ -90,7 +98,6 @@ const LocationSelect: React.FC<LocationSelectProps> = ({
control: base => ({
...base,
borderRadius: '0.75rem',
backgroundColor: '#F3F4F6',
border: '1px solid #E5E7EB',
padding: '2px',
'&:hover': {

View File

@@ -0,0 +1,23 @@
import { SearchResponse } from '@/app/types'
// получаем все предложения по выбранному owner_type
export async function fetchRoutes(category: string, query: string = ''): Promise<SearchResponse> {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/search/${category}/${query ? `?${query}` : ''}`,
{
cache: 'no-store',
}
)
if (!response.ok) {
throw new Error('Failed to fetch search results')
}
const data = await response.json()
return data
} catch (error) {
console.error('Error fetching search results:', error)
return { results: [], count: 0 }
}
}