diff --git a/backend/api/account/client/serializers.py b/backend/api/account/client/serializers.py index 3c50ccc..2369020 100644 --- a/backend/api/account/client/serializers.py +++ b/backend/api/account/client/serializers.py @@ -6,6 +6,26 @@ from api.models import UserProfile from django.shortcuts import get_object_or_404 import pytz +class CountrySerializer(serializers.ModelSerializer): + value = serializers.CharField(source='international_name') # для совместимости с селектом на фронте + label = serializers.SerializerMethodField() # для отображения в селекте + + class Meta: + model = Country + fields = ['id', 'value', 'label', 'flag_img_url'] + + def get_label(self, obj): + return obj.international_name or obj.official_name + +class CitySerializer(serializers.ModelSerializer): + value = serializers.CharField(source='name') # для совместимости с селектом + label = serializers.CharField(source='name') # для отображения в селекте + country_name = serializers.CharField(source='country.international_name') + + class Meta: + model = City + fields = ['id', 'value', 'label', 'country_name'] + class RouteSerializer(serializers.ModelSerializer): from_city_name = serializers.SerializerMethodField() to_city_name = serializers.SerializerMethodField() @@ -160,16 +180,16 @@ class CreateRouteSerializer(serializers.ModelSerializer): to_city = City.objects.get(name=validated_data.pop('city_to'), country=country_to) # обновляем номер телефона в профиле пользователя - contact_number = validated_data.pop('contact_number') + phone_number = validated_data.pop('phone_number') user_profile = get_object_or_404(UserProfile, user=self.context['request'].user) # проверяем, не используется ли этот номер другим пользователем - if UserProfile.objects.filter(phone_number=contact_number).exclude(user=self.context['request'].user).exists(): + if UserProfile.objects.filter(phone_number=phone_number).exclude(user=self.context['request'].user).exists(): raise serializers.ValidationError({ - "contact_number": "Этот номер телефона уже используется другим пользователем" + "phone_number": "Этот номер телефона уже используется другим пользователем" }) - user_profile.phone_number = contact_number + user_profile.phone_number = phone_number user_profile.save() # создаем маршрут diff --git a/backend/api/account/client/views.py b/backend/api/account/client/views.py index 7daedf1..843efc5 100644 --- a/backend/api/account/client/views.py +++ b/backend/api/account/client/views.py @@ -7,12 +7,13 @@ from rest_framework.response import Response from django.shortcuts import get_object_or_404 from django.core.validators import validate_email from django.core.exceptions import ValidationError +from django.db import models from api.auth.serializers import UserResponseSerializer from api.models import UserProfile from api.utils.decorators import handle_exceptions -from routes.models import Route -from .serializers import RouteSerializer, CreateRouteSerializer +from routes.models import Route, City, Country +from .serializers import RouteSerializer, CreateRouteSerializer, CitySerializer, CountrySerializer class UserDataView(ViewSet): permission_classes = [IsAuthenticated] @@ -95,8 +96,53 @@ class AccountActionsView(ViewSet): @action(detail=False, methods=['post']) @handle_exceptions - def create_sender_route(self, request): + def create_route(self, request): serializer = CreateRouteSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) + +class CityView(ViewSet): + + @action(detail=False, methods=['get']) + @handle_exceptions + def get_cities(self, request): + # получаем параметр country_id из query params + country_id = request.query_params.get('country_id') + + # базовый QuerySet + cities = City.objects.all() + + # фильтруем города по стране, если указан country_id + if country_id: + cities = cities.filter(country_id=country_id) + + # поиск по названию города + search = request.query_params.get('search') + if search: + cities = cities.filter(name__icontains=search) + + # ограничиваем количество результатов и сортируем по имени + cities = cities.order_by('name')[:100] + + return Response(CitySerializer(cities, many=True).data, status=status.HTTP_200_OK) + +class CountryView(ViewSet): + @action(detail=False, methods=['get']) + @handle_exceptions + def get_countries(self, request): + # базовый QuerySet + countries = Country.objects.all() + + # поиск по названию страны + search = request.query_params.get('search') + if search: + countries = countries.filter( + models.Q(international_name__icontains=search) | + models.Q(official_name__icontains=search) + ) + + # сортируем по международному названию + countries = countries.order_by('international_name') + + return Response(CountrySerializer(countries, many=True).data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/backend/api/urls.py b/backend/api/urls.py index c8877b3..a1af164 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -8,7 +8,7 @@ LoginViewSet, LogoutView, RefreshTokenView) -from api.account.client.views import UserDataView, AccountActionsView +from api.account.client.views import UserDataView, AccountActionsView, CityView, CountryView urlpatterns = [ path("v1/faq/", FAQView.as_view(), name='faqMain'), @@ -24,5 +24,8 @@ urlpatterns = [ path("v1/account/change_main_data/", AccountActionsView.as_view({'patch':'change_data_main_tab'}), name='change_data_main_tab'), path("v1/account/routes/", AccountActionsView.as_view({'get':'user_routes'}), name='user_routes'), - path("v1/account/create_sender/", AccountActionsView.as_view({'post':'create_sender_route'}), name='create_sender_route') + path("v1/account/create_route/", AccountActionsView.as_view({'post':'create_route'}), name='create_route'), + + path("v1/cities/", CityView.as_view({'get':'get_cities'}), name='get_cities'), + path("v1/countries/", CountryView.as_view({'get':'get_countries'}), name='get_countries') ] \ No newline at end of file diff --git a/frontend/app/(urls)/account/create-as-sender/page.tsx b/frontend/app/(urls)/account/create-as-sender/page.tsx index 13102e5..e9bdb44 100644 --- a/frontend/app/(urls)/account/create-as-sender/page.tsx +++ b/frontend/app/(urls)/account/create-as-sender/page.tsx @@ -1,12 +1,12 @@ 'use client' import React from 'react' -import MultiSelect from '@/components/ui/Selector' -import TextInput from '@/components/ui/TextInput' import PhoneInput from '@/components/ui/PhoneInput' import Button from '@/components/ui/Button' import TextAreaInput from '@/components/ui/TextAreaInput' import CheckboxInput from '@/components/ui/CheckboxInput' +import LocationSelect from '@/components/ui/LocationSelect' +import SingleSelect from '@/components/ui/SingleSelect' import { useForm } from '@/app/hooks/useForm' import useUserStore from '@/app/store/userStore' import showToast from '@/components/ui/Toast' @@ -29,10 +29,10 @@ const formatDateToHTML = (date: Date) => { const validationRules = { transport: { required: true }, - country_from: { required: true, minLength: 2 }, - city_from: { required: true, minLength: 2 }, - country_to: { required: true, minLength: 2 }, - city_to: { required: true, minLength: 2 }, + country_from_id: { required: true }, + city_from: { required: true }, + country_to_id: { required: true }, + city_to: { required: true }, cargo_type: { required: true }, departure: { required: true, @@ -61,6 +61,8 @@ const SenderPage = () => { city_from: '', country_to: '', city_to: '', + country_from_id: '', + country_to_id: '', cargo_type: '', departure: '', arrival: '', @@ -86,15 +88,26 @@ const SenderPage = () => { validationRules, async values => { try { - // await addNewSpecialist(values, selectedImage || undefined) - showToast({ - type: 'success', - message: 'Маршрут успешно создан!', + const response = await fetch('/api/account/sender', { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), }) - } catch { + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || 'Ошибка при обновлении данных') + } + + const result = await response.json() + setUser(result.user) + showToast({ type: 'success', message: 'Данные успешно обновлены!' }) + } catch (error) { showToast({ type: 'error', - message: 'Упс, что то пошло не так...', + message: error instanceof Error ? error.message : 'Ой, что то пошло не так..', }) } } @@ -115,45 +128,27 @@ const SenderPage = () => { {/* тип груза и транспорта */}