From f3233ae1750892ed51c8d4a1d5ff4d6f09deeeff Mon Sep 17 00:00:00 2001 From: Timofey Date: Thu, 22 May 2025 14:22:13 +0300 Subject: [PATCH] create RouteForm using sender page --- backend/api/account/client/serializers.py | 2 +- .../account/create-as-deliveler/page.tsx | 17 +- .../(urls)/account/create-as-sender/page.tsx | 360 +---------------- frontend/components/forms/RouteForm.tsx | 372 ++++++++++++++++++ 4 files changed, 387 insertions(+), 364 deletions(-) create mode 100644 frontend/components/forms/RouteForm.tsx diff --git a/backend/api/account/client/serializers.py b/backend/api/account/client/serializers.py index 7dd1854..c29c996 100644 --- a/backend/api/account/client/serializers.py +++ b/backend/api/account/client/serializers.py @@ -106,7 +106,7 @@ class CreateRouteSerializer(serializers.ModelSerializer): transport = serializers.ChoiceField(choices=type_transport_choices, source='type_transport') email_notification = serializers.BooleanField(source='receive_msg_by_email') phone_number = serializers.CharField(write_only=True) - owner_type = serializers.ChoiceField(choices=[('sender', 'Отправитель'), ('deliverer', 'Перевозчик')]) + owner_type = serializers.ChoiceField(choices=owner_type_choices) class Meta: model = Route diff --git a/frontend/app/(urls)/account/create-as-deliveler/page.tsx b/frontend/app/(urls)/account/create-as-deliveler/page.tsx index 9107171..093534a 100644 --- a/frontend/app/(urls)/account/create-as-deliveler/page.tsx +++ b/frontend/app/(urls)/account/create-as-deliveler/page.tsx @@ -1,17 +1,14 @@ import React from 'react' -import MultiSelect from '@/components/ui/Selector' +import RouteForm from '@/components/forms/RouteForm' const DelivelerPage = () => { return ( -
-
-
-
-

Перевезти посылку

-
-
-
-
+ ) } diff --git a/frontend/app/(urls)/account/create-as-sender/page.tsx b/frontend/app/(urls)/account/create-as-sender/page.tsx index 0ef5c93..d716a93 100644 --- a/frontend/app/(urls)/account/create-as-sender/page.tsx +++ b/frontend/app/(urls)/account/create-as-sender/page.tsx @@ -1,362 +1,16 @@ 'use client' import React from 'react' -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' -import { SenderPageProps, SelectOption } from '@/app/types' -import { - cargo_types, - cargo_type_translations, - transport_types, - transport_translations, -} from '@/app/constants' - -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') - const hours = String(date.getHours()).padStart(2, '0') - const minutes = String(date.getMinutes()).padStart(2, '0') - return `${year}-${month}-${day}T${hours}:${minutes}` -} - -const validationRules = { - transport: { required: true }, - 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, - pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/, - }, - arrival: { - required: true, - pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/, - }, - phone_number: { - required: true, - minLength: 11, - pattern: /^\+?[0-9]{11,}$/, - }, - comment: { required: false }, - email_notification: { required: false }, -} +import RouteForm from '@/components/forms/RouteForm' const SenderPage = () => { - const { user, setUser } = useUserStore() - - const today = formatDateToHTML(new Date()) - - const initialValues: SenderPageProps = { - transport: '', - country_from: '', - city_from: '', - country_to: '', - city_to: '', - country_from_id: '', - country_to_id: '', - cargo_type: '', - departure: '', - arrival: '', - phone_number: user?.phone_number || '', - comment: '', - email_notification: false, - } - - const cargoOptions: SelectOption[] = cargo_types.map((type, index) => ({ - id: index + 1, - value: type, - label: cargo_type_translations[type], - })) - - const transportOptions: SelectOption[] = transport_types.map((type, index) => ({ - id: index + 1, - value: type, - label: transport_translations[type], - })) - - const { values, handleChange, handleSubmit, resetField } = useForm( - initialValues, - validationRules, - async values => { - try { - // находим выбранные опции - const selectedTransport = transportOptions.find( - opt => opt.id.toString() === values.transport - ) - const selectedCargoType = cargoOptions.find(opt => opt.id.toString() === values.cargo_type) - - if (!selectedTransport || !selectedCargoType) { - throw new Error('Некорректный тип транспорта или груза') - } - - // подготавливаем данные для отправки - const requestData = { - ...values, - owner_type: 'sender', - transport: selectedTransport.value, - cargo_type: selectedCargoType.value, - phone_number: values.phone_number, - } - - const response = await fetch('/api/account/sender', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestData), - }) - - if (!response.ok) { - const error = await response.json() - console.error('Ошибка от сервера:', error) - throw new Error(error.error || 'Ошибка при создании маршрута') - } - - // Обновляем номер телефона в сторе, если он изменился - if (user && user.phone_number !== values.phone_number) { - setUser({ - ...user, - phone_number: values.phone_number, - }) - } - - showToast({ - type: 'success', - message: 'Маршрут успешно создан!', - }) - - // сбрасываем все поля формы кроме телефона - Object.keys(initialValues).forEach(field => { - if (field !== 'phone_number') { - resetField(field) - } - }) - } catch (error) { - console.error('Ошибка:', error) - showToast({ - type: 'error', - message: error instanceof Error ? error.message : 'Ой, что то пошло не так..', - }) - } - } - ) - return ( -
-
-
-
-
-

Отправить посылку

-

- Заполните информацию о вашей посылке и маршруте -

-
- - {/* тип груза и транспорта */} -
-
- -
- -
- -
-
- - {/* маршрут */} -
-

Маршрут

- -
-
- { - const selectedOption = e.target.selectedOption - handleChange({ - target: { - id: 'country_from', - value: e.target.value, - }, - }) - handleChange({ - target: { - id: 'country_from_id', - value: selectedOption?.id?.toString() || '', - }, - }) - handleChange({ - target: { - id: 'city_from', - value: '', - }, - }) - }} - label="Страна отправления" - placeholder="Выберите страну отправления" - /> - -
- -
- { - const selectedOption = e.target.selectedOption - handleChange({ - target: { - id: 'country_to', - value: e.target.value, - }, - }) - handleChange({ - target: { - id: 'country_to_id', - value: selectedOption?.id?.toString() || '', - }, - }) - handleChange({ - target: { - id: 'city_to', - value: '', - }, - }) - }} - label="Страна назначения" - placeholder="Выберите страну назначения" - /> - -
-
-
- - {/* даты */} -
-
- - -
- -
- - -
-
- - {/* контактная информация */} -

Контактная информация

-
- - - - - -
-
-
-
- - {/* кнопки действий */} -
-
-
+ ) } diff --git a/frontend/components/forms/RouteForm.tsx b/frontend/components/forms/RouteForm.tsx new file mode 100644 index 0000000..1502c4c --- /dev/null +++ b/frontend/components/forms/RouteForm.tsx @@ -0,0 +1,372 @@ +'use client' + +import React from 'react' +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' +import { SenderPageProps, SelectOption } from '@/app/types' +import { + cargo_types, + cargo_type_translations, + transport_types, + transport_translations, +} from '@/app/constants' + +interface RouteFormProps { + routeHandlerUrl: string + ownerType: 'customer' | 'mover' + title: string + description: string +} + +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') + const hours = String(date.getHours()).padStart(2, '0') + const minutes = String(date.getMinutes()).padStart(2, '0') + return `${year}-${month}-${day}T${hours}:${minutes}` +} + +const validationRules = { + transport: { required: true }, + 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, + pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/, + }, + arrival: { + required: true, + pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/, + }, + phone_number: { + required: true, + minLength: 11, + pattern: /^\+?[0-9]{11,}$/, + }, + comment: { required: false }, + email_notification: { required: false }, +} + +const RouteForm: React.FC = ({ + routeHandlerUrl, + ownerType, + title, + description, +}) => { + const { user, setUser } = useUserStore() + const today = formatDateToHTML(new Date()) + + const initialValues: SenderPageProps = { + transport: '', + country_from: '', + city_from: '', + country_to: '', + city_to: '', + country_from_id: '', + country_to_id: '', + cargo_type: '', + departure: '', + arrival: '', + phone_number: user?.phone_number || '', + comment: '', + email_notification: false, + } + + const cargoOptions: SelectOption[] = cargo_types.map((type, index) => ({ + id: index + 1, + value: type, + label: cargo_type_translations[type], + })) + + const transportOptions: SelectOption[] = transport_types.map((type, index) => ({ + id: index + 1, + value: type, + label: transport_translations[type], + })) + + const { values, handleChange, handleSubmit, resetField } = useForm( + initialValues, + validationRules, + async values => { + try { + // находим выбранные опции + const selectedTransport = transportOptions.find( + opt => opt.id.toString() === values.transport + ) + const selectedCargoType = cargoOptions.find(opt => opt.id.toString() === values.cargo_type) + + if (!selectedTransport || !selectedCargoType) { + throw new Error('Некорректный тип транспорта или груза') + } + + // подготавливаем данные для отправки + const requestData = { + ...values, + owner_type: ownerType, + transport: selectedTransport.value, + cargo_type: selectedCargoType.value, + phone_number: values.phone_number, + } + + const response = await fetch(routeHandlerUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestData), + }) + + if (!response.ok) { + const error = await response.json() + console.error('Ошибка от сервера:', error) + throw new Error(error.error || 'Ошибка при создании маршрута') + } + + // Обновляем номер телефона в сторе, если он изменился + if (user && user.phone_number !== values.phone_number) { + setUser({ + ...user, + phone_number: values.phone_number, + }) + } + + showToast({ + type: 'success', + message: 'Маршрут успешно создан!', + }) + + // сбрасываем все поля формы кроме телефона + Object.keys(initialValues).forEach(field => { + if (field !== 'phone_number') { + resetField(field) + } + }) + } catch (error) { + console.error('Ошибка:', error) + showToast({ + type: 'error', + message: error instanceof Error ? error.message : 'Ой, что то пошло не так..', + }) + } + } + ) + + return ( +
+
+
+
+
+

{title}

+

{description}

+
+ + {/* тип груза и транспорта */} +
+
+ +
+ +
+ +
+
+ + {/* маршрут */} +
+

Маршрут

+ +
+
+ { + const selectedOption = e.target.selectedOption + handleChange({ + target: { + id: 'country_from', + value: e.target.value, + }, + }) + handleChange({ + target: { + id: 'country_from_id', + value: selectedOption?.id?.toString() || '', + }, + }) + handleChange({ + target: { + id: 'city_from', + value: '', + }, + }) + }} + label="Страна отправления" + placeholder="Выберите страну отправления" + /> + +
+ +
+ { + const selectedOption = e.target.selectedOption + handleChange({ + target: { + id: 'country_to', + value: e.target.value, + }, + }) + handleChange({ + target: { + id: 'country_to_id', + value: selectedOption?.id?.toString() || '', + }, + }) + handleChange({ + target: { + id: 'city_to', + value: '', + }, + }) + }} + label="Страна назначения" + placeholder="Выберите страну назначения" + /> + +
+
+
+ + {/* даты */} +
+
+ + +
+ +
+ + +
+
+ + {/* контактная информация */} +

Контактная информация

+
+ + + + + +
+
+
+
+ + {/* кнопки действий */} +
+
+
+ ) +} + +export default RouteForm