Improve delivery workflows and add versioned guided tours
This commit is contained in:
@@ -1,417 +1,137 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useRef, useState, type FormEvent } from 'react';
|
||||
import { AlertCircle, Loader2 } from 'lucide-react';
|
||||
import { Button, Input, Select, Modal } from '../ui';
|
||||
import { deliveryRequestSourceOptions, pickupOptions } from '../../constants/pickup';
|
||||
import { formatDateForInput, parseDateFromInput, getTodayFrontend } from '../../utils/date';
|
||||
import type { Delivery, DeliveryRequestSource, PickupLocation, DeliveryStatus, PaymentStatus } from '../../types';
|
||||
import type { Delivery, DeliveryRequestSource, PickupLocation, PaymentStatus } from '../../types';
|
||||
|
||||
type DeliveryInput = Omit<Delivery, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
interface DeliveryFormProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (delivery: Omit<Delivery, 'id' | 'createdAt' | 'updatedAt'>) => void | Promise<void>;
|
||||
onSubmit: (delivery: DeliveryInput) => void | Promise<void>;
|
||||
initialData?: Delivery | null;
|
||||
defaultDate?: string;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
// Phone validation regex for Kazakhstan numbers
|
||||
const PHONE_REGEX = /^\+7\s?\(?\d{3}\)?\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/;
|
||||
const requestSourceOptions = [{ value: '', label: 'Выберите источник заявки' }, ...deliveryRequestSourceOptions];
|
||||
const paymentOptions = [{ value: 'prepaid', label: 'Предоплата' }, { value: 'paid', label: 'Полная оплата' }];
|
||||
const emptyDelivery = (date: string): DeliveryInput => ({
|
||||
date, pickupLocation: 'warehouse', pickupLocation2: null,
|
||||
warehouseRequestSource: null, warehouseRequestSource2: null,
|
||||
productName: '', productName2: '', customerName: '', address: '', street: '', house: '',
|
||||
apartment: '', entrance: '', floor: '', phone: '', additionalPhone: '', hasElevator: false,
|
||||
paymentStatus: 'prepaid', serviceInfo: '', comment: '', status: 'new',
|
||||
});
|
||||
|
||||
// City is not shown in UI but is included in the saved address (used for 2GIS search).
|
||||
const CITY_LABEL = 'Кокшетау';
|
||||
const requestSourceOptions = [
|
||||
{ value: '', label: 'Выберите источник заявки' },
|
||||
...deliveryRequestSourceOptions,
|
||||
];
|
||||
const paymentStatusOptions = [
|
||||
{ value: 'prepaid', label: 'Предоплата' },
|
||||
{ value: 'paid', label: 'Полная оплата' },
|
||||
];
|
||||
// A fresh mount on each opening prevents edited data leaking into a new delivery.
|
||||
export const DeliveryForm = (props: DeliveryFormProps) => props.isOpen
|
||||
? <DeliveryFormContent {...props} key={props.initialData?.id || 'new'} /> : null;
|
||||
|
||||
const buildAddressString = (
|
||||
street: string,
|
||||
house: string,
|
||||
apartment: string,
|
||||
entrance: string,
|
||||
): string => {
|
||||
const parts: string[] = [CITY_LABEL];
|
||||
if (street) parts.push(`ул. ${street}`);
|
||||
if (house) parts.push(`д. ${house}`);
|
||||
if (apartment) parts.push(`кв. ${apartment}`);
|
||||
if (entrance) parts.push(`подъезд ${entrance}`);
|
||||
return parts.join(', ');
|
||||
};
|
||||
|
||||
export const DeliveryForm = ({ isOpen, onClose, onSubmit, initialData, defaultDate, isSubmitting }: DeliveryFormProps) => {
|
||||
const [formData, setFormData] = useState({
|
||||
date: defaultDate || getTodayFrontend(),
|
||||
pickupLocation: 'warehouse' as PickupLocation,
|
||||
pickupLocation2: null as PickupLocation | null,
|
||||
warehouseRequestSource: null as DeliveryRequestSource | null,
|
||||
warehouseRequestSource2: null as DeliveryRequestSource | null,
|
||||
productName: '',
|
||||
productName2: '',
|
||||
customerName: '',
|
||||
address: '',
|
||||
street: '',
|
||||
house: '',
|
||||
apartment: '',
|
||||
entrance: '',
|
||||
floor: '',
|
||||
phone: '',
|
||||
additionalPhone: '',
|
||||
hasElevator: false,
|
||||
paymentStatus: 'prepaid' as PaymentStatus,
|
||||
serviceInfo: '',
|
||||
comment: '',
|
||||
status: 'new' as DeliveryStatus,
|
||||
});
|
||||
const [showSecondPickup, setShowSecondPickup] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setFormData({
|
||||
date: initialData.date,
|
||||
pickupLocation: initialData.pickupLocation,
|
||||
pickupLocation2: initialData.pickupLocation2 || null,
|
||||
warehouseRequestSource: initialData.warehouseRequestSource || null,
|
||||
warehouseRequestSource2: initialData.warehouseRequestSource2 || null,
|
||||
productName: initialData.productName,
|
||||
productName2: initialData.productName2 || '',
|
||||
customerName: initialData.customerName,
|
||||
address: initialData.address,
|
||||
street: initialData.street,
|
||||
house: initialData.house,
|
||||
apartment: initialData.apartment || '',
|
||||
entrance: initialData.entrance || '',
|
||||
floor: initialData.floor || '',
|
||||
phone: initialData.phone,
|
||||
additionalPhone: initialData.additionalPhone || '',
|
||||
hasElevator: initialData.hasElevator,
|
||||
paymentStatus: initialData.paymentStatus || 'prepaid',
|
||||
serviceInfo: initialData.serviceInfo || '',
|
||||
comment: initialData.comment,
|
||||
status: initialData.status,
|
||||
});
|
||||
setShowSecondPickup(!!initialData.pickupLocation2);
|
||||
} else if (defaultDate) {
|
||||
setFormData(prev => ({ ...prev, date: defaultDate }));
|
||||
}
|
||||
}, [initialData, defaultDate, isOpen]);
|
||||
|
||||
const validatePhone = useCallback((phone: string): boolean => {
|
||||
if (!phone) return false;
|
||||
return PHONE_REGEX.test(phone);
|
||||
}, []);
|
||||
|
||||
const isPhoneValid = !formData.phone || validatePhone(formData.phone);
|
||||
const isAdditionalPhoneValid = !formData.additionalPhone || validatePhone(formData.additionalPhone);
|
||||
const isWarehouseRequestSourceValid = formData.pickupLocation !== 'warehouse' || !!formData.warehouseRequestSource;
|
||||
const isWarehouseRequestSource2Valid = !showSecondPickup || formData.pickupLocation2 !== 'warehouse' || !!formData.warehouseRequestSource2;
|
||||
const isFormValid = formData.productName && formData.phone && isPhoneValid && formData.customerName && formData.street && formData.house && isWarehouseRequestSourceValid && isWarehouseRequestSource2Valid;
|
||||
|
||||
const handlePickupLocationChange = (pickupLocation: PickupLocation) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
pickupLocation,
|
||||
warehouseRequestSource: pickupLocation === 'warehouse' ? formData.warehouseRequestSource : null,
|
||||
});
|
||||
function DeliveryFormContent({ onClose, onSubmit, initialData, defaultDate }: DeliveryFormProps) {
|
||||
const [baseline] = useState<DeliveryInput>(() => initialData ? { ...initialData } : emptyDelivery(defaultDate || getTodayFrontend()));
|
||||
const [data, setData] = useState<DeliveryInput>(baseline);
|
||||
const [secondPickup, setSecondPickup] = useState(!!baseline.pickupLocation2);
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [attempted, setAttempted] = useState(false);
|
||||
const submitting = useRef(false);
|
||||
const dirty = JSON.stringify(data) !== JSON.stringify(baseline);
|
||||
const update = <K extends keyof DeliveryInput>(key: K, value: DeliveryInput[K]) => setData(prev => ({ ...prev, [key]: value }));
|
||||
const phoneError = data.phone && !PHONE_REGEX.test(data.phone) ? 'Введите номер в формате +7 (776) 567-89-01' : undefined;
|
||||
const extraPhoneError = data.additionalPhone && !PHONE_REGEX.test(data.additionalPhone) ? 'Проверьте дополнительный номер или очистите поле' : undefined;
|
||||
const sourceMissing = data.pickupLocation === 'warehouse' && !data.warehouseRequestSource;
|
||||
const secondSourceMissing = secondPickup && data.pickupLocation2 === 'warehouse' && !data.warehouseRequestSource2;
|
||||
const requestClose = () => {
|
||||
if (submitting.current) return;
|
||||
if (!dirty || window.confirm('Закрыть форму? Несохранённые изменения будут потеряны.')) onClose();
|
||||
};
|
||||
|
||||
const handlePickupLocation2Change = (pickupLocation2: PickupLocation) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
pickupLocation2,
|
||||
warehouseRequestSource2: pickupLocation2 === 'warehouse' ? formData.warehouseRequestSource2 : null,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
setAttempted(true);
|
||||
if (submitting.current) return;
|
||||
if (phoneError || extraPhoneError || sourceMissing || secondSourceMissing) {
|
||||
setError('Проверьте отмеченные поля. Данные остаются в форме.');
|
||||
return;
|
||||
}
|
||||
submitting.current = true;
|
||||
setSubmitting(true);
|
||||
setError('');
|
||||
const address = ['Кокшетау', `ул. ${data.street.trim()}`, `д. ${data.house.trim()}`,
|
||||
data.apartment && `кв. ${data.apartment}`, data.entrance && `подъезд ${data.entrance}`].filter(Boolean).join(', ');
|
||||
try {
|
||||
const payload = {
|
||||
...formData,
|
||||
address: buildAddressString(formData.street, formData.house, formData.apartment, formData.entrance),
|
||||
};
|
||||
await onSubmit(payload);
|
||||
if (!initialData) {
|
||||
setFormData({
|
||||
date: defaultDate || getTodayFrontend(),
|
||||
pickupLocation: 'warehouse',
|
||||
pickupLocation2: null,
|
||||
warehouseRequestSource: null,
|
||||
warehouseRequestSource2: null,
|
||||
productName: '',
|
||||
productName2: '',
|
||||
customerName: '',
|
||||
address: '',
|
||||
street: '',
|
||||
house: '',
|
||||
apartment: '',
|
||||
entrance: '',
|
||||
floor: '',
|
||||
phone: '',
|
||||
additionalPhone: '',
|
||||
hasElevator: false,
|
||||
paymentStatus: 'prepaid',
|
||||
serviceInfo: '',
|
||||
comment: '',
|
||||
status: 'new',
|
||||
});
|
||||
setShowSecondPickup(false);
|
||||
}
|
||||
await onSubmit({ ...data, productName: data.productName.trim(), customerName: data.customerName.trim(), street: data.street.trim(), house: data.house.trim(), address });
|
||||
onClose();
|
||||
} catch {
|
||||
// Error is handled by parent, keep form open
|
||||
setError('Не удалось сохранить доставку. Проверьте соединение и попробуйте ещё раз. Все введённые данные сохранены в форме.');
|
||||
} finally {
|
||||
submitting.current = false;
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={initialData ? 'Редактировать доставку' : 'Новая доставка'}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" onClick={onClose} disabled={isSubmitting}>
|
||||
Отмена
|
||||
<Modal isOpen onClose={requestClose} closeDisabled={isSubmitting} title={initialData ? 'Редактировать доставку' : 'Новая доставка'}
|
||||
footer={<div className="w-full">
|
||||
{error && <div role="alert" className="mb-3 flex items-start gap-2 rounded-lg bg-red-50 p-3 text-sm text-red-800"><AlertCircle size={18} className="mt-0.5 shrink-0" /><p>{error}</p></div>}
|
||||
<div className="flex justify-end gap-3"><Button variant="ghost" onClick={requestClose} disabled={isSubmitting}>Отмена</Button>
|
||||
<Button type="submit" form="delivery-form" disabled={isSubmitting}>
|
||||
{isSubmitting && <Loader2 size={18} className="mr-2 animate-spin" />}
|
||||
{isSubmitting ? 'Сохранение…' : initialData ? 'Сохранить' : 'Создать доставку'}
|
||||
</Button>
|
||||
<Button type="submit" form="delivery-form" disabled={isSubmitting || !isFormValid}>
|
||||
{isSubmitting ? 'Сохранение...' : initialData ? 'Сохранить' : 'Создать'}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form id="delivery-form" onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#1b1b1d] mb-1">
|
||||
Дата доставки
|
||||
</div>
|
||||
</div>}>
|
||||
<form id="delivery-form" onSubmit={handleSubmit} className="space-y-7" aria-busy={isSubmitting}>
|
||||
<p className="text-sm text-[#666870]">Поля со звёздочкой обязательны.</p>
|
||||
<fieldset disabled={isSubmitting} className="space-y-4">
|
||||
<legend className="mb-3 text-base font-semibold">Дата и загрузка</legend>
|
||||
<Input label="Дата доставки *" type="date" value={formatDateForInput(data.date)} required onChange={e => update('date', parseDateFromInput(e.target.value))} />
|
||||
<Select label="Место загрузки" value={data.pickupLocation} options={pickupOptions} onChange={e => {
|
||||
const pickupLocation = e.target.value as PickupLocation;
|
||||
setData(prev => ({ ...prev, pickupLocation, warehouseRequestSource: pickupLocation === 'warehouse' ? prev.warehouseRequestSource : null }));
|
||||
}} />
|
||||
{data.pickupLocation === 'warehouse' && <Select label="От кого заявка *" value={data.warehouseRequestSource || ''} options={requestSourceOptions} required
|
||||
error={attempted && sourceMissing ? 'Выберите источник заявки' : undefined}
|
||||
onChange={e => update('warehouseRequestSource', e.target.value as DeliveryRequestSource || null)} />}
|
||||
<Input label="Название товара *" value={data.productName} required pattern=".*\S.*" onChange={e => update('productName', e.target.value)} placeholder="Например, диван и два кресла" />
|
||||
<label className="flex min-h-11 cursor-pointer items-center gap-3 text-sm font-medium">
|
||||
<input type="checkbox" className="h-5 w-5" checked={secondPickup} onChange={e => {
|
||||
setSecondPickup(e.target.checked);
|
||||
setData(prev => ({ ...prev, pickupLocation2: e.target.checked ? 'warehouse' : null, warehouseRequestSource2: null, productName2: '' }));
|
||||
}} />Добавить вторую точку загрузки
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={formatDateForInput(formData.date)}
|
||||
onChange={(e) => setFormData({ ...formData, date: parseDateFromInput(e.target.value) })}
|
||||
className="w-full px-3 py-2 bg-[#f5f3f5] border border-[#c5c6cd] rounded-md text-[#1b1b1d] focus:outline-none focus:ring-2 focus:ring-[#1B263B] focus:border-transparent transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
label="Место загрузки"
|
||||
value={formData.pickupLocation}
|
||||
onChange={(e) => handlePickupLocationChange(e.target.value as PickupLocation)}
|
||||
options={pickupOptions}
|
||||
/>
|
||||
|
||||
{formData.pickupLocation === 'warehouse' && (
|
||||
<Select
|
||||
label="От кого заявка *"
|
||||
value={formData.warehouseRequestSource || ''}
|
||||
onChange={(e) => setFormData({ ...formData, warehouseRequestSource: e.target.value ? e.target.value as DeliveryRequestSource : null })}
|
||||
options={requestSourceOptions}
|
||||
required
|
||||
error={!isWarehouseRequestSourceValid ? 'Выберите, от кого исходит заявка' : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Название товара"
|
||||
value={formData.productName}
|
||||
onChange={(e) => setFormData({ ...formData, productName: e.target.value })}
|
||||
placeholder="Введите название товара"
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Address fields */}
|
||||
<div className="bg-[#f5f3f5] rounded-lg p-4 space-y-3">
|
||||
<p className="text-sm font-medium text-[#1b1b1d]">Адрес доставки</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs text-[#75777d] mb-1">Улица *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.street}
|
||||
onChange={(e) => setFormData({ ...formData, street: e.target.value })}
|
||||
className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#75777d] mb-1">Дом *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.house}
|
||||
onChange={(e) => setFormData({ ...formData, house: e.target.value })}
|
||||
className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#75777d] mb-1">Квартира</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.apartment}
|
||||
onChange={(e) => setFormData({ ...formData, apartment: e.target.value })}
|
||||
className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#75777d] mb-1">Подъезд</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.entrance}
|
||||
onChange={(e) => setFormData({ ...formData, entrance: e.target.value })}
|
||||
className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#75777d] mb-1">Этаж</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.floor}
|
||||
onChange={(e) => setFormData({ ...formData, floor: e.target.value })}
|
||||
className="w-full px-2 py-1.5 bg-white border border-[#c5c6cd] rounded text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="ФИО клиента *"
|
||||
value={formData.customerName}
|
||||
onChange={(e) => setFormData({ ...formData, customerName: e.target.value })}
|
||||
placeholder="Иванов Иван Иванович"
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Телефон покупателя *"
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||
onFocus={(e) => {
|
||||
if (!e.target.value) {
|
||||
setFormData({ ...formData, phone: '+7' });
|
||||
}
|
||||
}}
|
||||
placeholder="+7 (776)-567-89-01"
|
||||
required
|
||||
aria-invalid={!isPhoneValid}
|
||||
aria-describedby={!isPhoneValid ? 'phone-error' : undefined}
|
||||
/>
|
||||
{!isPhoneValid && formData.phone && (
|
||||
<p id="phone-error" className="text-sm text-red-500 mt-1">
|
||||
Введите корректный номер: +7 (XXX) XXX-XX-XX
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Дополнительный номер телефона"
|
||||
type="tel"
|
||||
value={formData.additionalPhone}
|
||||
onChange={(e) => setFormData({ ...formData, additionalPhone: e.target.value })}
|
||||
onFocus={(e) => {
|
||||
if (!e.target.value) {
|
||||
setFormData({ ...formData, additionalPhone: '+7' });
|
||||
}
|
||||
}}
|
||||
placeholder="+7 (776)-567-89-01"
|
||||
aria-invalid={!isAdditionalPhoneValid}
|
||||
aria-describedby={!isAdditionalPhoneValid ? 'additional-phone-error' : undefined}
|
||||
/>
|
||||
{!isAdditionalPhoneValid && formData.additionalPhone && (
|
||||
<p id="additional-phone-error" className="text-sm text-red-500 mt-1">
|
||||
Введите корректный номер: +7 (XXX) XXX-XX-XX
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Second pickup location */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="hasSecondPickup"
|
||||
checked={showSecondPickup}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
setShowSecondPickup(checked);
|
||||
setFormData({
|
||||
...formData,
|
||||
pickupLocation2: checked ? formData.pickupLocation2 || 'warehouse' : null,
|
||||
warehouseRequestSource2: checked ? formData.warehouseRequestSource2 : null,
|
||||
productName2: checked ? formData.productName2 : '',
|
||||
});
|
||||
}}
|
||||
className="w-4 h-4 text-[#1B263B] border-[#c5c6cd] rounded focus:ring-[#1B263B]"
|
||||
/>
|
||||
<label htmlFor="hasSecondPickup" className="text-sm text-[#1b1b1d]">
|
||||
Добавить вторую точку загрузки
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{showSecondPickup && (
|
||||
<div className="bg-[#f5f3f5] rounded-lg p-4 space-y-3">
|
||||
<p className="text-sm font-medium text-[#1b1b1d]">Вторая точка загрузки</p>
|
||||
<Select
|
||||
label="Место загрузки 2"
|
||||
value={formData.pickupLocation2 || ''}
|
||||
onChange={(e) => handlePickupLocation2Change(e.target.value as PickupLocation)}
|
||||
options={pickupOptions}
|
||||
/>
|
||||
{formData.pickupLocation2 === 'warehouse' && (
|
||||
<Select
|
||||
label="От кого заявка 2 *"
|
||||
value={formData.warehouseRequestSource2 || ''}
|
||||
onChange={(e) => setFormData({ ...formData, warehouseRequestSource2: e.target.value ? e.target.value as DeliveryRequestSource : null })}
|
||||
options={requestSourceOptions}
|
||||
required
|
||||
error={!isWarehouseRequestSource2Valid ? 'Выберите, от кого исходит заявка' : undefined}
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
label="Название товара 2"
|
||||
value={formData.productName2}
|
||||
onChange={(e) => setFormData({ ...formData, productName2: e.target.value })}
|
||||
placeholder="Название товара со второй точки"
|
||||
/>
|
||||
{secondPickup && <div className="space-y-4 border-t border-[#e4e2e4] pt-4">
|
||||
<Select label="Второе место загрузки" value={data.pickupLocation2 || 'warehouse'} options={pickupOptions} onChange={e => {
|
||||
const pickupLocation2 = e.target.value as PickupLocation;
|
||||
setData(prev => ({ ...prev, pickupLocation2, warehouseRequestSource2: pickupLocation2 === 'warehouse' ? prev.warehouseRequestSource2 : null }));
|
||||
}} />
|
||||
{data.pickupLocation2 === 'warehouse' && <Select label="От кого вторая заявка *" value={data.warehouseRequestSource2 || ''} options={requestSourceOptions} required
|
||||
error={attempted && secondSourceMissing ? 'Выберите источник второй заявки' : undefined}
|
||||
onChange={e => update('warehouseRequestSource2', e.target.value as DeliveryRequestSource || null)} />}
|
||||
<Input label="Товар со второй точки" value={data.productName2 || ''} onChange={e => update('productName2', e.target.value)} />
|
||||
</div>}
|
||||
</fieldset>
|
||||
<fieldset disabled={isSubmitting} className="space-y-4 border-t border-[#e4e2e4] pt-5">
|
||||
<legend className="text-base font-semibold">Адрес доставки</legend>
|
||||
<Input label="Улица *" value={data.street} required pattern=".*\S.*" autoComplete="address-line1" onChange={e => update('street', e.target.value)} />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Input label="Дом *" value={data.house} required pattern=".*\S.*" onChange={e => update('house', e.target.value)} />
|
||||
<Input label="Квартира" value={data.apartment || ''} onChange={e => update('apartment', e.target.value)} />
|
||||
<Input label="Подъезд" value={data.entrance || ''} onChange={e => update('entrance', e.target.value)} />
|
||||
<Input label="Этаж" value={data.floor || ''} onChange={e => update('floor', e.target.value)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="hasElevator"
|
||||
checked={formData.hasElevator}
|
||||
onChange={(e) => setFormData({ ...formData, hasElevator: e.target.checked })}
|
||||
className="w-4 h-4 text-[#1B263B] border-[#c5c6cd] rounded focus:ring-[#1B263B]"
|
||||
/>
|
||||
<label htmlFor="hasElevator" className="text-sm text-[#1b1b1d]">
|
||||
Наличие лифта
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
label="Оплата"
|
||||
value={formData.paymentStatus}
|
||||
onChange={(e) => setFormData({ ...formData, paymentStatus: e.target.value as PaymentStatus })}
|
||||
options={paymentStatusOptions}
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Услуги (сборка, подъём)"
|
||||
value={formData.serviceInfo}
|
||||
onChange={(e) => setFormData({ ...formData, serviceInfo: e.target.value })}
|
||||
placeholder="Сборка 5000 тг, подъём на этаж 3000 тг"
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Комментарий"
|
||||
value={formData.comment}
|
||||
onChange={(e) => setFormData({ ...formData, comment: e.target.value })}
|
||||
placeholder="Дополнительная информация..."
|
||||
/>
|
||||
<label className="flex min-h-11 cursor-pointer items-center gap-3 text-sm font-medium"><input type="checkbox" className="h-5 w-5" checked={data.hasElevator} onChange={e => update('hasElevator', e.target.checked)} />Есть лифт</label>
|
||||
</fieldset>
|
||||
<fieldset disabled={isSubmitting} className="space-y-4 border-t border-[#e4e2e4] pt-5">
|
||||
<legend className="text-base font-semibold">Клиент и оплата</legend>
|
||||
<Input label="ФИО клиента *" value={data.customerName} required pattern=".*\S.*" autoComplete="name" onChange={e => update('customerName', e.target.value)} />
|
||||
<Input label="Телефон покупателя *" type="tel" inputMode="tel" autoComplete="tel" value={data.phone} required placeholder="+7 (776) 567-89-01" error={phoneError}
|
||||
onChange={e => update('phone', e.target.value)} />
|
||||
<Input label="Дополнительный телефон" type="tel" inputMode="tel" value={data.additionalPhone || ''} placeholder="Необязательно" error={extraPhoneError} onChange={e => update('additionalPhone', e.target.value)} />
|
||||
<Select label="Оплата" value={data.paymentStatus} options={paymentOptions} onChange={e => update('paymentStatus', e.target.value as PaymentStatus)} />
|
||||
<Input label="Услуги: сборка, подъём" value={data.serviceInfo || ''} placeholder="Например, сборка 5000 тг" onChange={e => update('serviceInfo', e.target.value)} />
|
||||
<Input label="Комментарий" value={data.comment} placeholder="Что важно знать доставщику" onChange={e => update('comment', e.target.value)} />
|
||||
</fieldset>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user