create RouteForm using sender page
This commit is contained in:
@@ -106,7 +106,7 @@ class CreateRouteSerializer(serializers.ModelSerializer):
|
|||||||
transport = serializers.ChoiceField(choices=type_transport_choices, source='type_transport')
|
transport = serializers.ChoiceField(choices=type_transport_choices, source='type_transport')
|
||||||
email_notification = serializers.BooleanField(source='receive_msg_by_email')
|
email_notification = serializers.BooleanField(source='receive_msg_by_email')
|
||||||
phone_number = serializers.CharField(write_only=True)
|
phone_number = serializers.CharField(write_only=True)
|
||||||
owner_type = serializers.ChoiceField(choices=[('sender', 'Отправитель'), ('deliverer', 'Перевозчик')])
|
owner_type = serializers.ChoiceField(choices=owner_type_choices)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Route
|
model = Route
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import MultiSelect from '@/components/ui/Selector'
|
import RouteForm from '@/components/forms/RouteForm'
|
||||||
|
|
||||||
const DelivelerPage = () => {
|
const DelivelerPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<RouteForm
|
||||||
<div className="overflow-hidden rounded-2xl shadow">
|
routeHandlerUrl="/api/account/sender"
|
||||||
<div className="bg-white p-6 sm:p-8">
|
ownerType="customer"
|
||||||
<div className="space-y-4">
|
title="Перевезти посылку"
|
||||||
<h1 className="text-2xl">Перевезти посылку</h1>
|
description="Заполните информацию о вашей посылке и маршруте"
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,362 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PhoneInput from '@/components/ui/PhoneInput'
|
import RouteForm from '@/components/forms/RouteForm'
|
||||||
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 },
|
|
||||||
}
|
|
||||||
|
|
||||||
const SenderPage = () => {
|
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<SenderPageProps>(
|
|
||||||
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 (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<RouteForm
|
||||||
<div className="overflow-hidden rounded-2xl bg-white shadow">
|
routeHandlerUrl="/api/account/sender"
|
||||||
<div className="p-6 sm:p-8">
|
ownerType="mover"
|
||||||
<div className="space-y-8">
|
title="Отправить посылку"
|
||||||
<div>
|
description="Заполните информацию о вашей посылке и маршруте"
|
||||||
<h1 className="text-2xl font-semibold text-gray-900">Отправить посылку</h1>
|
/>
|
||||||
<p className="mt-1 text-sm text-gray-600">
|
|
||||||
Заполните информацию о вашей посылке и маршруте
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* тип груза и транспорта */}
|
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<SingleSelect
|
|
||||||
name="cargo_type"
|
|
||||||
value={values.cargo_type}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Тип груза"
|
|
||||||
placeholder="Выберите тип груза"
|
|
||||||
options={cargoOptions}
|
|
||||||
className="mt-1"
|
|
||||||
noOptionsMessage="Нет доступных типов груза"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<SingleSelect
|
|
||||||
name="transport"
|
|
||||||
value={values.transport}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Способ перевозки"
|
|
||||||
placeholder="Выберите способ перевозки"
|
|
||||||
options={transportOptions}
|
|
||||||
className="mt-1"
|
|
||||||
noOptionsMessage="Нет доступных способов перевозки"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* маршрут */}
|
|
||||||
<div>
|
|
||||||
<h2 className="mb-2 text-xl font-medium text-gray-900">Маршрут</h2>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<LocationSelect
|
|
||||||
name="country_from"
|
|
||||||
value={values.country_from}
|
|
||||||
handleChange={e => {
|
|
||||||
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="Выберите страну отправления"
|
|
||||||
/>
|
|
||||||
<LocationSelect
|
|
||||||
name="city_from"
|
|
||||||
value={values.city_from}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Город отправления"
|
|
||||||
placeholder="Выберите город отправления"
|
|
||||||
countryId={values.country_from_id}
|
|
||||||
isCity
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<LocationSelect
|
|
||||||
name="country_to"
|
|
||||||
value={values.country_to}
|
|
||||||
handleChange={e => {
|
|
||||||
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="Выберите страну назначения"
|
|
||||||
/>
|
|
||||||
<LocationSelect
|
|
||||||
name="city_to"
|
|
||||||
value={values.city_to}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Город назначения"
|
|
||||||
placeholder="Выберите город назначения"
|
|
||||||
countryId={values.country_to_id}
|
|
||||||
isCity
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* даты */}
|
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="departure" className="block text-sm font-medium text-gray-700">
|
|
||||||
Дата отправления
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
name="departure"
|
|
||||||
id="departure"
|
|
||||||
value={values.departure}
|
|
||||||
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>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="arrival" className="block text-sm font-medium text-gray-700">
|
|
||||||
Дата прибытия
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
name="arrival"
|
|
||||||
id="arrival"
|
|
||||||
value={values.arrival}
|
|
||||||
onChange={handleChange}
|
|
||||||
min={values.departure || 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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* контактная информация */}
|
|
||||||
<h2 className="mb-2 text-xl font-medium text-gray-900">Контактная информация</h2>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<PhoneInput
|
|
||||||
value={values.phone_number}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Контактный телефон"
|
|
||||||
operatorsInfo={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextAreaInput
|
|
||||||
value={values.comment}
|
|
||||||
handleChange={handleChange}
|
|
||||||
label="Комментарий"
|
|
||||||
placeholder="Дополнительная информация о грузе или пожелания"
|
|
||||||
name="comment"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CheckboxInput
|
|
||||||
name="email_notification"
|
|
||||||
label="Уведомления"
|
|
||||||
checked={values.email_notification}
|
|
||||||
handleChange={handleChange}
|
|
||||||
enabledText="Хочу получать уведомления по email"
|
|
||||||
disabledText="Не получать уведомления"
|
|
||||||
info="Вы будете получать уведомления о важных событиях на указанный email адрес"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* кнопки действий */}
|
|
||||||
<div className="flex justify-end space-x-4">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
text="Отмена"
|
|
||||||
className="border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
text="Создать маршрут"
|
|
||||||
className="bg-orange hover:bg-orange/80 focus:ring-red w-1/3 border border-gray-300 py-2 text-sm font-medium text-white shadow-sm focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
372
frontend/components/forms/RouteForm.tsx
Normal file
372
frontend/components/forms/RouteForm.tsx
Normal file
@@ -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<RouteFormProps> = ({
|
||||||
|
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<SenderPageProps>(
|
||||||
|
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 (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="overflow-hidden rounded-2xl bg-white shadow">
|
||||||
|
<div className="p-6 sm:p-8">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-semibold text-gray-900">{title}</h1>
|
||||||
|
<p className="mt-1 text-sm text-gray-600">{description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* тип груза и транспорта */}
|
||||||
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<SingleSelect
|
||||||
|
name="cargo_type"
|
||||||
|
value={values.cargo_type}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Тип груза"
|
||||||
|
placeholder="Выберите тип груза"
|
||||||
|
options={cargoOptions}
|
||||||
|
className="mt-1"
|
||||||
|
noOptionsMessage="Нет доступных типов груза"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<SingleSelect
|
||||||
|
name="transport"
|
||||||
|
value={values.transport}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Способ перевозки"
|
||||||
|
placeholder="Выберите способ перевозки"
|
||||||
|
options={transportOptions}
|
||||||
|
className="mt-1"
|
||||||
|
noOptionsMessage="Нет доступных способов перевозки"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* маршрут */}
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-2 text-xl font-medium text-gray-900">Маршрут</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<LocationSelect
|
||||||
|
name="country_from"
|
||||||
|
value={values.country_from}
|
||||||
|
handleChange={e => {
|
||||||
|
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="Выберите страну отправления"
|
||||||
|
/>
|
||||||
|
<LocationSelect
|
||||||
|
name="city_from"
|
||||||
|
value={values.city_from}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Город отправления"
|
||||||
|
placeholder="Выберите город отправления"
|
||||||
|
countryId={values.country_from_id}
|
||||||
|
isCity
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<LocationSelect
|
||||||
|
name="country_to"
|
||||||
|
value={values.country_to}
|
||||||
|
handleChange={e => {
|
||||||
|
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="Выберите страну назначения"
|
||||||
|
/>
|
||||||
|
<LocationSelect
|
||||||
|
name="city_to"
|
||||||
|
value={values.city_to}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Город назначения"
|
||||||
|
placeholder="Выберите город назначения"
|
||||||
|
countryId={values.country_to_id}
|
||||||
|
isCity
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* даты */}
|
||||||
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="departure" className="block text-sm font-medium text-gray-700">
|
||||||
|
Дата отправления
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
name="departure"
|
||||||
|
id="departure"
|
||||||
|
value={values.departure}
|
||||||
|
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>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="arrival" className="block text-sm font-medium text-gray-700">
|
||||||
|
Дата прибытия
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
name="arrival"
|
||||||
|
id="arrival"
|
||||||
|
value={values.arrival}
|
||||||
|
onChange={handleChange}
|
||||||
|
min={values.departure || 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* контактная информация */}
|
||||||
|
<h2 className="mb-2 text-xl font-medium text-gray-900">Контактная информация</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<PhoneInput
|
||||||
|
value={values.phone_number}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Контактный телефон"
|
||||||
|
operatorsInfo={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextAreaInput
|
||||||
|
value={values.comment}
|
||||||
|
handleChange={handleChange}
|
||||||
|
label="Комментарий"
|
||||||
|
placeholder="Дополнительная информация о грузе или пожелания"
|
||||||
|
name="comment"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CheckboxInput
|
||||||
|
name="email_notification"
|
||||||
|
label="Уведомления"
|
||||||
|
checked={values.email_notification}
|
||||||
|
handleChange={handleChange}
|
||||||
|
enabledText="Хочу получать уведомления по email"
|
||||||
|
disabledText="Не получать уведомления"
|
||||||
|
info="Вы будете получать уведомления о важных событиях на указанный email адрес"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* кнопки действий */}
|
||||||
|
<div className="flex justify-end space-x-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
text="Отмена"
|
||||||
|
className="border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
text="Создать маршрут"
|
||||||
|
className="bg-orange hover:bg-orange/80 focus:ring-red w-1/3 border border-gray-300 py-2 text-sm font-medium text-white shadow-sm focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouteForm
|
||||||
Reference in New Issue
Block a user