backend logic

This commit is contained in:
2025-05-24 14:55:27 +03:00
parent e4fcf0716d
commit b755eda4b5
7 changed files with 160 additions and 31 deletions

View File

@@ -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
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

View File

@@ -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
)

View File

@@ -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'),

View File

@@ -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
}

View File

@@ -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')

View File

@@ -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) => {
/>
<TextInput
name="price"
value={values.price}
name="moving_price"
value={values.moving_price}
handleChange={handleChange}
label="Предлагаемая цена"
placeholder="Укажите стоимость перевозки"
style="register"
/>
<TextInput
name="deliveryTime"
value={values.deliveryTime}
handleChange={handleChange}
label="Срок доставки"
placeholder="Укажите предполагаемый срок доставки"
style="register"
/>
<div>
<label htmlFor="moving_date" className="block text-sm font-medium text-gray-700">
Срок доставки
</label>
<input
type="date"
name="moving_date"
id="moving_date"
value={values.moving_date}
onChange={handleChange}
min={today}
className="mt-1 block w-full rounded-xl border border-gray-300 px-3 py-2 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
/>
</div>
<TextAreaInput
name="comment"
value={values.comment}

View File

@@ -0,0 +1,46 @@
import { Lead } from '@/app/types'
export const sendLead = async (data: Lead) => {
const API_URL = process.env.NEXT_PUBLIC_API_URL
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: 'application/json',
}
try {
const body = JSON.stringify({
name: data.name,
phone_number: data.phone_number,
email: data.email,
moving_price: data.moving_price,
moving_date: data.moving_date,
comment: data.comment,
})
const response = await fetch(`${API_URL}/account/send_lead/`, {
method: 'POST',
headers,
body,
})
if (!response.ok) {
let errorMessage = `Failed to send lead data: ${response.status} ${response.statusText}`
try {
const errorData = await response.text()
if (errorData) {
errorMessage += ` - ${errorData}`
}
} catch (e) {
console.error('Error parsing error response:', e)
}
throw new Error(errorMessage)
}
const text = await response.text()
return text ? JSON.parse(text) : null
} catch (error) {
console.error('Error sending lead data:', error)
throw error
}
}