diff --git a/backend/api/account/client/serializers.py b/backend/api/account/client/serializers.py
index 4579800..087bd27 100644
--- a/backend/api/account/client/serializers.py
+++ b/backend/api/account/client/serializers.py
@@ -1,5 +1,5 @@
from rest_framework import serializers
-from routes.models import Route, City, Country
+from routes.models import Route, City, Country, Leads
from django.conf import settings
from routes.constants.routeChoices import cargo_type_choices, type_transport_choices, owner_type_choices
from routes.constants.account_types import account_types
@@ -7,6 +7,8 @@ from api.models import UserProfile
from sitemanagement.models import Pricing
from django.shortcuts import get_object_or_404
import pytz
+from django.utils import timezone
+from django.contrib.auth.models import User
class CountrySerializer(serializers.ModelSerializer):
value = serializers.CharField(source='international_name') # для совместимости с селектом на фронте
@@ -266,5 +268,20 @@ class PlanChangeSerializer(serializers.Serializer):
instance.save()
return instance
-class LeadSerializer(serializers.Serializer):
- pass
\ No newline at end of file
+class LeadSerializer(serializers.ModelSerializer):
+ route = serializers.PrimaryKeyRelatedField(queryset=Route.objects.all())
+ moving_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
+
+ class Meta:
+ model = Leads
+ fields = ['route', 'moving_user', 'moving_price', 'moving_date', 'comment']
+
+ def validate_moving_date(self, value):
+ if value < timezone.now().date():
+ raise serializers.ValidationError("Дата перевозки не может быть в прошлом")
+ return value
+
+ def validate_moving_price(self, value):
+ if value <= 0:
+ raise serializers.ValidationError("Цена должна быть больше нуля")
+ return value
\ No newline at end of file
diff --git a/backend/api/account/client/views.py b/backend/api/account/client/views.py
index 1a370bf..192aa74 100644
--- a/backend/api/account/client/views.py
+++ b/backend/api/account/client/views.py
@@ -13,9 +13,9 @@ from django.contrib.auth.models import User
from api.auth.serializers import UserResponseSerializer
from api.models import UserProfile
from api.utils.decorators import handle_exceptions
-from routes.models import Route, City, Country
+from routes.models import Route, City, Country, Leads
from sitemanagement.models import Pricing
-from .serializers import RouteSerializer, CreateRouteSerializer, CitySerializer, CountrySerializer, PlanChangeSerializer, PricingSerializer
+from .serializers import RouteSerializer, CreateRouteSerializer, CitySerializer, CountrySerializer, PlanChangeSerializer, PricingSerializer, LeadSerializer
class UserDataView(ViewSet):
"""Эндпоинт для наполнения стора фронта данными"""
@@ -193,9 +193,56 @@ class GetMembershipData(ViewSet):
return Response(serializer.data, status=status.HTTP_200_OK)
class LeadViewSet(ViewSet):
- """Собираем лиды"""
+ """ViewSet для работы с заявками на перевозку"""
+ permission_classes = [IsAuthenticated]
+
@action(detail=False, methods=['post'])
@handle_exceptions
def send_lead(self, request, id):
- pass
+ """
+ Создание новой заявки на перевозку
+ """
+ # добавляем текущего пользователя в данные
+ data = request.data.copy()
+ data['moving_user'] = request.user.id
+
+ # проверяем существование и доступность маршрута
+ try:
+ route = Route.objects.get(id=data.get('route'))
+ if route.owner == request.user:
+ return Response(
+ {"error": "Вы не можете откликнуться на собственную заявку"},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+ except Route.DoesNotExist:
+ return Response(
+ {"error": "Указанный маршрут не найден"},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ serializer = LeadSerializer(data=data)
+ if serializer.is_valid():
+ lead = serializer.save()
+
+ # собираем ответ с данными о заявке для фронта
+ response_data = {
+ "status": "success",
+ "message": "Заявка успешно создана",
+ "data": {
+ "id": lead.id,
+ "route_id": lead.route.id,
+ "moving_price": lead.moving_price,
+ "moving_date": lead.moving_date,
+ }
+ }
+ return Response(response_data, status=status.HTTP_201_CREATED)
+
+ return Response(
+ {
+ "status": "error",
+ "message": "Ошибка валидации данных",
+ "errors": serializer.errors
+ },
+ status=status.HTTP_400_BAD_REQUEST
+ )
\ No newline at end of file
diff --git a/backend/api/urls.py b/backend/api/urls.py
index d92a070..ef11fcc 100644
--- a/backend/api/urls.py
+++ b/backend/api/urls.py
@@ -36,7 +36,7 @@ urlpatterns = [
path("v1/account/routes/", AccountActionsView.as_view({'get':'user_routes'}), name='user_routes'),
path("v1/account/create_route/", AccountActionsView.as_view({'post':'create_route'}), name='create_route'),
- path("v1/account/sendlead/", LeadViewSet.as_view({'post':'send_lead'}), name='send_lead'),
+ path("v1/account/send_lead/", LeadViewSet.as_view({'post':'send_lead'}), name='send_lead'),
path("v1/cities/", CityView.as_view({'get':'get_cities'}), name='get_cities'),
path("v1/countries/", CountryView.as_view({'get':'get_countries'}), name='get_countries'),
diff --git a/frontend/app/types/index.ts b/frontend/app/types/index.ts
index 19e318f..303da08 100644
--- a/frontend/app/types/index.ts
+++ b/frontend/app/types/index.ts
@@ -236,3 +236,12 @@ export interface SearchPageProps {
params: Promise<{ category?: string }>
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>
}
+
+export interface Lead {
+ name: string
+ phone_number: string
+ email: string
+ moving_price: string
+ moving_date: string
+ comment: string
+}
diff --git a/frontend/components/forms/RouteForm.tsx b/frontend/components/forms/RouteForm.tsx
index 1502c4c..18b212c 100644
--- a/frontend/components/forms/RouteForm.tsx
+++ b/frontend/components/forms/RouteForm.tsx
@@ -25,7 +25,7 @@ interface RouteFormProps {
description: string
}
-const formatDateToHTML = (date: Date) => {
+export const formatDateToHTML = (date: Date) => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
diff --git a/frontend/components/popups/LeadPopup.tsx b/frontend/components/popups/LeadPopup.tsx
index e73f1b9..d245aed 100644
--- a/frontend/components/popups/LeadPopup.tsx
+++ b/frontend/components/popups/LeadPopup.tsx
@@ -7,13 +7,14 @@ import TextInput from '../ui/TextInput'
import TextAreaInput from '../ui/TextAreaInput'
import PhoneInput from '../ui/PhoneInput'
import useUserStore from '@/app/store/userStore'
+import { sendLead } from '@/lib/main/sendLead'
const validationRules = {
name: { required: true },
phone_number: { required: true },
email: { required: true },
- price: { required: false },
- deliveryTime: { required: false },
+ moving_price: { required: true },
+ moving_date: { required: true },
comment: { required: false },
}
@@ -26,21 +27,24 @@ interface LeadPopupProps {
const LeadPopup = ({ id, isOpen, onClose, onSuccess }: LeadPopupProps) => {
const { user } = useUserStore()
+ const today = new Date().toISOString().split('T')[0]
+
+ const initialValues = {
+ name: user?.name || '',
+ phone_number: user?.phone_number || '',
+ email: user?.email || '',
+ moving_price: '',
+ moving_date: '',
+ comment: '',
+ id: id,
+ }
const { values, handleChange, handleSubmit, setValues } = useForm(
- {
- name: user?.name || '',
- phone_number: user?.phone_number || '',
- email: user?.email || '',
- price: '',
- deliveryTime: '',
- comment: '',
- id: id,
- },
+ initialValues,
validationRules,
async values => {
try {
- // await sendLead(values)
+ await sendLead(values)
showToast({
type: 'success',
message: 'Сообщение отправлено!',
@@ -98,22 +102,28 @@ const LeadPopup = ({ id, isOpen, onClose, onSuccess }: LeadPopupProps) => {
/>