diff --git a/backend/api/search/serializers.py b/backend/api/search/serializers.py
index bfa408f..f23494f 100644
--- a/backend/api/search/serializers.py
+++ b/backend/api/search/serializers.py
@@ -1,8 +1,81 @@
-from api.main.serializers import HomePageRouteSerializer
-from routes.models import Route
+from rest_framework import serializers
+from routes.models import Route, Country
+from api.main.serializers import RouteSerializer
+class SearchRouteSerializer(RouteSerializer):
+ id = serializers.IntegerField()
+ username = serializers.SerializerMethodField()
+ owner_type = serializers.CharField()
+ from_city_name = serializers.SerializerMethodField('get_start_point')
+ from_country_name = serializers.SerializerMethodField('get_country_from')
+ to_city_name = serializers.SerializerMethodField('get_end_point')
+ to_country_name = serializers.SerializerMethodField('get_country_to')
+ formatted_cargo_type = serializers.SerializerMethodField('get_cargo_type')
+ formatted_transport = serializers.SerializerMethodField('get_moving_type')
+ type_transport = serializers.CharField()
+ userImg = serializers.SerializerMethodField()
+ comment = serializers.CharField()
+ formatted_departure = serializers.DateTimeField(source='departure_DT')
+ formatted_arrival = serializers.DateTimeField(source='arrival_DT')
+ country_from_icon = serializers.SerializerMethodField()
+ country_to_icon = serializers.SerializerMethodField()
-class SearchRouteSerializer(HomePageRouteSerializer):
- class Meta(HomePageRouteSerializer.Meta):
+ class Meta:
model = Route
- fields = HomePageRouteSerializer.Meta.fields
+ fields = (
+ 'id', 'username', 'owner_type', 'from_city_name', 'from_country_name',
+ 'to_city_name', 'to_country_name', 'formatted_cargo_type',
+ 'formatted_transport', 'type_transport', 'userImg', 'comment',
+ 'formatted_departure', 'formatted_arrival', 'country_from_icon',
+ 'country_to_icon'
+ )
+
+ def get_username(self, obj):
+ return obj.owner.first_name if obj.owner else None
+
+ def get_userImg(self, obj):
+ try:
+ if obj.owner and hasattr(obj.owner, 'userprofile') and obj.owner.userprofile.image:
+ return obj.owner.userprofile.image.url
+ return None
+ except Exception as e:
+ print(f"Error in get_userImg: {e}")
+ return None
+
+ def get_country_from_icon(self, obj):
+ country = self.get_from_country_name(obj)
+ if not country:
+ return None
+ try:
+ country_obj = Country.objects.get(international_name=country)
+ return country_obj.flag_img_url
+ except Country.DoesNotExist:
+ return None
+
+ def get_country_to_icon(self, obj):
+ country = self.get_to_country_name(obj)
+ if not country:
+ return None
+ try:
+ country_obj = Country.objects.get(international_name=country)
+ return country_obj.flag_img_url
+ except Country.DoesNotExist:
+ return None
+
+ def get_start_point(self, obj):
+ return self.get_from_city_name(obj)
+
+ def get_country_from(self, obj):
+ return self.get_from_country_name(obj)
+
+ def get_end_point(self, obj):
+ return self.get_to_city_name(obj)
+
+ def get_country_to(self, obj):
+ return self.get_to_country_name(obj)
+
+ def get_cargo_type(self, obj):
+ return self.get_formatted_cargo_type(obj)
+
+ def get_moving_type(self, obj):
+ return self.get_formatted_transport(obj)
diff --git a/backend/api/search/views.py b/backend/api/search/views.py
index 0e0e137..a639fb7 100644
--- a/backend/api/search/views.py
+++ b/backend/api/search/views.py
@@ -12,6 +12,9 @@ class SearchRouteListView(generics.ListAPIView):
def get_queryset(self):
owner_type = self.kwargs.get('owner_type')
+ from_city = self.request.query_params.get('from')
+ to_city = self.request.query_params.get('to')
+
valid_types = [choice[0] for choice in owner_type_choices]
current_time = timezone.now()
@@ -23,6 +26,12 @@ class SearchRouteListView(generics.ListAPIView):
owner_type=owner_type,
status="actual")
+ # фильтруем по городам если они указаны
+ if from_city:
+ queryset = queryset.filter(from_city__name__iexact=from_city)
+ if to_city:
+ queryset = queryset.filter(to_city__name__iexact=to_city)
+
# фильтруем по времени в зависимости от типа
if owner_type == 'mover':
queryset = queryset.filter(departure_DT__gt=current_time)
diff --git a/frontend/app/(urls)/news/[slug]/page.tsx b/frontend/app/(urls)/news/[slug]/page.tsx
index 2868bf2..b1935a3 100644
--- a/frontend/app/(urls)/news/[slug]/page.tsx
+++ b/frontend/app/(urls)/news/[slug]/page.tsx
@@ -1,7 +1,54 @@
import React from 'react'
+import Image from 'next/image'
+import { getNews } from '@/lib/main/fetchNews'
+import { notFound } from 'next/navigation'
-const page = () => {
- return
page
+interface PageProps {
+ params: Promise<{
+ slug: string
+ }>
}
-export default page
+async function getNewsItem(slug: string) {
+ const news = await getNews()
+ return news.find(item => item.slug === slug)
+}
+
+export default async function NewsPage({ params }: PageProps) {
+ const { slug } = await params
+ const newsItem = await getNewsItem(slug)
+
+ if (!newsItem) {
+ notFound()
+ }
+
+ return (
+
+
+
+
+
+
+ {newsItem.title}
+
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/app/(urls)/search/[category]/[route]/page.tsx b/frontend/app/(urls)/search/[category]/[route]/page.tsx
index 99e9ebc..7bb532e 100644
--- a/frontend/app/(urls)/search/[category]/[route]/page.tsx
+++ b/frontend/app/(urls)/search/[category]/[route]/page.tsx
@@ -1,5 +1,7 @@
import React, { Suspense } from 'react'
+import type { Metadata } from 'next'
import SearchCard from '../../components/SearchCard'
+import { SearchCardProps } from '@/app/types'
interface SearchPageProps {
params: {
@@ -8,10 +10,59 @@ interface SearchPageProps {
}
}
-// дернуть search api для from-to параметров
async function fetchSearch(category: string, from: string, to: string) {
- // get search api(owner_type, from, to)
- return []
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/search/${category}/?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`,
+ {
+ cache: 'no-store',
+ }
+ )
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch search results')
+ }
+
+ const data = await response.json()
+ return {
+ results: data.results,
+ count: data.results.length,
+ }
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: SearchPageProps['params']
+}): Promise {
+ const [fromCity, toCity] = params.route.split('-')
+
+ return {
+ title: `Поиск ${params.category === 'mover' ? 'перевозчика' : 'посылки'} ${fromCity} → ${toCity} | Tripwb`,
+ description: `Найдите ${params.category === 'mover' ? 'перевозчика' : 'посылку'} по маршруту ${fromCity} → ${toCity} | Tripwb`,
+ openGraph: {
+ title: `Поиск ${params.category === 'mover' ? 'перевозчика' : 'посылки'} ${fromCity} → ${toCity} | Tripwb`,
+ description: `Найдите ${params.category === 'mover' ? 'перевозчика' : 'посылку'} по маршруту ${fromCity} → ${toCity}`,
+ url: `https://tripwb.com/search/${params.category}/${params.route}`,
+ 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',
+ },
+ alternates: {
+ canonical: `https://tripwb.com/search/${params.category}/${params.route}`,
+ },
+ robots: {
+ index: true,
+ follow: true,
+ },
+ }
}
export default async function SearchPage(props: SearchPageProps) {
@@ -19,7 +70,7 @@ export default async function SearchPage(props: SearchPageProps) {
const { category, route } = params
const [fromCity, toCity] = route.split('-')
- const initialData = await fetchSearch(category, fromCity, toCity)
+ const { results, count } = await fetchSearch(category, fromCity, toCity)
return (
@@ -33,14 +84,12 @@ export default async function SearchPage(props: SearchPageProps) {
Загрузка результатов...}>
- {/* результаты поиска */}
- {initialData.map((item: any, index: number) => (
-
- {/* Здесь будет карточка с результатом */}
-
Результат поиска {index + 1}
-
- ))}
+ {results.length > 0 ? (
+ results.map((item: SearchCardProps) =>
)
+ ) : (
+
По данному маршруту ничего не найдено
+ )}
diff --git a/frontend/app/(urls)/search/components/SearchCard.tsx b/frontend/app/(urls)/search/components/SearchCard.tsx
index 56af4b9..bdac3f2 100644
--- a/frontend/app/(urls)/search/components/SearchCard.tsx
+++ b/frontend/app/(urls)/search/components/SearchCard.tsx
@@ -108,12 +108,14 @@ const SearchCard = ({
onClick={handleLeadClick}
/>
-
-
-
-
{comment}
+ {comment && (
+
-
+ )}
+
Объявление № {id}
diff --git a/frontend/app/favicon.ico b/frontend/app/favicon.ico
index 718d6fe..067fc6c 100644
Binary files a/frontend/app/favicon.ico and b/frontend/app/favicon.ico differ
diff --git a/frontend/app/types/index.ts b/frontend/app/types/index.ts
index 81db2b2..4133297 100644
--- a/frontend/app/types/index.ts
+++ b/frontend/app/types/index.ts
@@ -70,6 +70,8 @@ export interface NewsItem {
content: string
titleImage: string
slug: string
+ path: string
+ filename: string
}
export interface NewsProps {
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 093ca61..ec31bce 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -13,7 +13,7 @@ const nextConfig: NextConfig = {
protocol: 'http', // для локал девеломпента
hostname: '127.0.0.1',
port: '8000',
- pathname: '/media/uploads/**',
+ pathname: '/media/**',
},
{
protocol: 'https',