From 45e5e78df23855a5a381213e05ce47fbb2bead02 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 23 May 2025 11:48:45 +0300 Subject: [PATCH] dynamic routes for main page --- backend/api/main/serializers.py | 92 +++++++++++- backend/api/main/views.py | 18 ++- .../(urls)/search/components/SearchCard.tsx | 142 +++++++++++------- frontend/app/constants/index.ts | 48 ------ frontend/app/page.tsx | 11 +- frontend/app/types/index.ts | 33 ++-- frontend/lib/main/fetchFirstRoutes.ts | 24 +++ frontend/next.config.ts | 7 +- frontend/public/images/belarus.png | Bin 10872 -> 0 bytes frontend/public/images/russia.png | Bin 8406 -> 0 bytes 10 files changed, 234 insertions(+), 141 deletions(-) create mode 100644 frontend/lib/main/fetchFirstRoutes.ts delete mode 100644 frontend/public/images/belarus.png delete mode 100644 frontend/public/images/russia.png diff --git a/backend/api/main/serializers.py b/backend/api/main/serializers.py index 2e4db1a..5b8c8cd 100644 --- a/backend/api/main/serializers.py +++ b/backend/api/main/serializers.py @@ -1,9 +1,7 @@ from rest_framework import serializers -from routes.models import Route from sitemanagement.models import FAQ, News -from django.conf import settings -import pytz -from routes.constants.routeChoices import cargo_type_choices, type_transport_choices +from routes.models import Country +from api.account.client.serializers import RouteSerializer class FAQMainSerializer(serializers.ModelSerializer): class Meta: @@ -37,4 +35,88 @@ class TelegramSerializer(serializers.Serializer): def create(self, validated_data): return type('TelegramMessage', (), validated_data) - \ No newline at end of file + + +class HomePageRouteSerializer(RouteSerializer): + username = serializers.SerializerMethodField() + userImg = serializers.SerializerMethodField() + start_point = serializers.SerializerMethodField() + country_from = serializers.SerializerMethodField() + country_from_icon = serializers.SerializerMethodField() + country_from_code = serializers.SerializerMethodField() + end_point = serializers.SerializerMethodField() + country_to = serializers.SerializerMethodField() + country_to_icon = serializers.SerializerMethodField() + country_to_code = serializers.SerializerMethodField() + cargo_type = serializers.SerializerMethodField() + user_request = serializers.SerializerMethodField() + user_comment = serializers.CharField(source='comment') + moving_type = serializers.SerializerMethodField() + estimated_date = serializers.SerializerMethodField() + day_out = serializers.DateTimeField(source='departure_DT') + day_in = serializers.DateTimeField(source='arrival_DT') + + 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_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_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_from_code(self, obj): + country = self.get_from_country_name(obj) + return country[:3].upper() if country else None + + 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_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: + print(f"Country not found: {country}") + return None + + def get_country_to_code(self, obj): + country = self.get_to_country_name(obj) + return country[:3].upper() if country else None + + def get_cargo_type(self, obj): + return self.get_formatted_cargo_type(obj) + + def get_user_request(self, obj): + return 'Нужен перевозчик' if obj.owner_type == 'customer' else 'Могу перевезти' + + def get_moving_type(self, obj): + return self.get_formatted_transport(obj) + + def get_estimated_date(self, obj): + return obj.arrival_DT \ No newline at end of file diff --git a/backend/api/main/views.py b/backend/api/main/views.py index 7197550..f9d5d09 100644 --- a/backend/api/main/views.py +++ b/backend/api/main/views.py @@ -4,12 +4,10 @@ from rest_framework import status from rest_framework.views import APIView from rest_framework.response import Response from api.utils.decorators import handle_exceptions -from django.db.models import Q from routes.models import Route from routes.constants.routeChoices import owner_type_choices -from api.main.serializers import FAQMainSerializer, NewsMainSerializer, TelegramSerializer -from api.account.client.serializers import RouteSerializer +from api.main.serializers import FAQMainSerializer, NewsMainSerializer, TelegramSerializer, HomePageRouteSerializer from sitemanagement.models import FAQ, News class FAQView(APIView): @@ -39,16 +37,20 @@ class NewsView(APIView): class LatestRoutesView(APIView): @handle_exceptions def get(self, request): - """Получаем последние 5 маршрутов для каждого типа owner_type""" + """Получаем последние маршруты""" - latest_routes = {} + routes = [] owner_types = dict(owner_type_choices).keys() for owner_type in owner_types: - routes = Route.objects.filter(owner_type=owner_type).order_by('-id')[:5] - latest_routes[owner_type] = RouteSerializer(routes, many=True).data + routes.extend( + HomePageRouteSerializer( + Route.objects.filter(owner_type=owner_type).order_by('-id')[:5], + many=True + ).data + ) - return Response(latest_routes, status=status.HTTP_200_OK) + return Response(routes, status=status.HTTP_200_OK) class TelegramMessageView(APIView): @handle_exceptions diff --git a/frontend/app/(urls)/search/components/SearchCard.tsx b/frontend/app/(urls)/search/components/SearchCard.tsx index ac1306b..5410cd6 100644 --- a/frontend/app/(urls)/search/components/SearchCard.tsx +++ b/frontend/app/(urls)/search/components/SearchCard.tsx @@ -1,42 +1,43 @@ import React from 'react' import Image from 'next/image' import Button from '@/components/ui/Button' -import { SearchCardProps } from '@/app/types/index' +import { SearchCardProps } from '@/app/types' +import noPhoto from '../../../../public/images/noPhoto.png' const SearchCard = ({ id, username, + owner_type, + from_city_name, + from_country_name, + to_city_name, + to_country_name, + formatted_cargo_type, + formatted_transport, + type_transport, userImg, - start_point, - country_from, + comment, + formatted_departure, + formatted_arrival, country_from_icon, - country_from_code, - end_point, - country_to, country_to_icon, - country_to_code, - cargo_type, - user_request, - user_comment, - moving_type, - estimated_date, - day_out, - day_in, }: SearchCardProps) => { const getUserRequestStyles = () => { - if (user_request === 'Нужен перевозчик') { + if (owner_type === 'customer') { return 'text-[#065bff]' } return 'text-[#45c226]' } const setMovingTypeIcon = () => { - if (moving_type === 'Авиатранспорт') { + if (type_transport === 'air') { return '/images/airplane.png' } return '/images/car.png' } + const userRequest = owner_type === 'customer' ? 'Нужен перевозчик' : 'Могу перевезти' + return ( <> {/* десктоп */} @@ -46,21 +47,22 @@ const SearchCard = ({
{username}
{username}
|
- {user_request} + {userRequest}
- Тип посылки: {cargo_type} + Тип посылки:{' '} + {formatted_cargo_type}
@@ -72,30 +74,37 @@ const SearchCard = ({
- {user_comment} + {comment}
Объявление № {id}
- {user_request === 'Нужен перевозчик' ? ( + {userRequest === 'Нужен перевозчик' ? ( Забрать из: ) : ( Выезжаю из: )}
- {country_from_code} - {country_from_code} + {from_country_name.substring(0, + + {from_country_name.substring(0, 3).toUpperCase()} + - {start_point} / {country_from} + {from_city_name} / {from_country_name}
- {user_request === 'Могу перевезти' && ( + {userRequest === 'Могу перевезти' && (
Отправление:{' '} - {day_out?.toLocaleDateString()} + {formatted_departure}
)}
@@ -103,7 +112,7 @@ const SearchCard = ({
- {moving_type} + {formatted_transport}
- {user_request === 'Нужен перевозчик' && ( + {userRequest === 'Нужен перевозчик' && (
Дата доставки:{' '} - - {estimated_date.toLocaleDateString()} - + {formatted_arrival}
)}
- {user_request === 'Нужен перевозчик' ? ( + {userRequest === 'Нужен перевозчик' ? (
Доставить в:
) : (
Прибываю в:
@@ -143,16 +150,23 @@ const SearchCard = ({
- {country_to_code} - {country_to_code} + {to_country_name.substring(0, + + {to_country_name.substring(0, 3).toUpperCase()} + - {end_point} / {country_to} + {to_city_name} / {to_country_name}
- {user_request === 'Могу перевезти' && ( + {userRequest === 'Могу перевезти' && (
Прибытие:{' '} - {day_in?.toLocaleDateString()} + {formatted_arrival}
)}
@@ -165,28 +179,26 @@ const SearchCard = ({
-
{user_request}
+
{userRequest}
- Тип посылки: {cargo_type} + Тип посылки: {formatted_cargo_type}
{username}
-
- {user_comment} -
+
{comment}
Объявление № {id}
- {user_request === 'Нужен перевозчик' ? ( + {userRequest === 'Нужен перевозчик' ? ( Забрать из: ) : ( Выезжаю из: @@ -207,16 +219,23 @@ const SearchCard = ({
- {country_from_code} - {country_from_code} + {from_country_name.substring(0, + + {from_country_name.substring(0, 3).toUpperCase()} + - {start_point} / {country_from} + {from_city_name} / {from_country_name}
- {moving_type} + {formatted_transport}
-
Дата доставки: {estimated_date.toLocaleDateString()}
+
Дата доставки: {formatted_arrival}
- {user_request === 'Нужен перевозчик' ? ( + {userRequest === 'Нужен перевозчик' ? (
Доставить в:
) : (
Прибываю в:
)}
- {country_to_code} - {country_to_code} + {to_country_name.substring(0, + + {to_country_name.substring(0, 3).toUpperCase()} + - {end_point} / {country_to} + {to_city_name} / {to_country_name}
- {user_request === 'Могу перевезти' && ( + {userRequest === 'Могу перевезти' && (
Прибытие:{' '} - {day_in?.toLocaleDateString()} + {formatted_arrival}
)}
diff --git a/frontend/app/constants/index.ts b/frontend/app/constants/index.ts index c5fbf65..da2e213 100644 --- a/frontend/app/constants/index.ts +++ b/frontend/app/constants/index.ts @@ -1,55 +1,7 @@ -import avatar from '../../public/images/avatar.png' -import belarusIcon from '../../public/images/belarus.png' -import russiaIcon from '../../public/images/russia.png' import { CargoType, TransportType } from '../types' -const userImg = avatar -const blIcon = belarusIcon -const ruIcon = russiaIcon - export const routes = 12845 -export const data = [ - { - id: 1123, - username: 'John Doe', - userImg: userImg, - start_point: 'Минск', - country_from: 'Беларусь', - end_point: 'Москва', - country_to: 'Россия', - cargo_type: 'Документы', - user_request: 'Нужен перевозчик', - user_comment: 'Нужно перевезти документы из Минска в Москву', - country_from_icon: blIcon, - country_to_icon: ruIcon, - country_from_code: 'BY', - country_to_code: 'RU', - moving_type: 'Авиатранспорт', - estimated_date: new Date(2025, 4, 15), - }, - { - id: 2423, - username: 'John Doe', - userImg: userImg, - start_point: 'Минск', - country_from: 'Беларусь', - end_point: 'Москва', - country_to: 'Россия', - cargo_type: 'Документы', - user_request: 'Могу перевезти', - user_comment: 'Нужно перевезти документы из Минска в Москву', - moving_type: 'Автоперевозка', - estimated_date: new Date(2025, 5, 18), - country_from_icon: blIcon, - country_to_icon: ruIcon, - country_from_code: 'BY', - country_to_code: 'RU', - day_out: new Date(2025, 5, 21), - day_in: new Date(2025, 5, 25), - }, -] - export const cargo_types: CargoType[] = ['letter', 'package', 'passenger', 'parcel', 'cargo'] export const cargo_type_translations: Record = { diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index a5d56d5..6c352f4 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -3,16 +3,17 @@ import Image from 'next/image' import AddressSelector from '@/components/AddressSelector' import SearchCard from '@/app/(urls)/search/components/SearchCard' import FAQ from '@/components/FAQ' -import { data } from '@/app/constants' import { routes } from '@/app/constants' import Button from '@/components/ui/Button' import News from '@/components/News' import { getFAQs } from '@/lib/main/fetchFAQ' import { getNews } from '@/lib/main/fetchNews' +import { getFirstRoutes } from '@/lib/main/fetchFirstRoutes' export default async function Home() { const faqs = await getFAQs() const news = await getNews() + const latestRoutes = await getFirstRoutes() return (
@@ -69,9 +70,11 @@ export default async function Home() { {/* первые пять серч карточек -- бекенд??? */}
- {data.map(card => ( - - ))} + {Array.isArray(latestRoutes) && latestRoutes.length > 0 ? ( + latestRoutes.map(card => ) + ) : ( +
Нет доступных маршрутов
+ )}