Files
tripwithbonus/frontend/components/forms/RouteForm.tsx
2025-05-28 12:30:08 +03:00

373 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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
}
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')
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: true,
}
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