Files
aiform_dev/frontend/src/components/form/Step2Details.tsx
AI Assistant e26ec2941a feat: Добавлены технические DEV-кнопки для быстрой навигации по шагам
🔧 Технические панели на всех шагах:
- Step 1: Кнопка пропуска валидации полиса → Step 2
- Step 2: Кнопки навигации Назад/Вперёд без валидации полей
- Step 3: Автоподтверждение телефона + быстрая отправка заявки

Теперь можно тестировать весь флоу без заполнения обязательных полей.
2025-10-28 10:13:18 +03:00

403 lines
14 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.

import { Form, Input, Button, Select, DatePicker, Upload, message, Spin, Alert } from 'antd';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
import { useState } from 'react';
import type { UploadFile } from 'antd/es/upload/interface';
import dayjs from 'dayjs';
const { Option } = Select;
interface Props {
formData: any;
updateFormData: (data: any) => void;
onNext: () => void;
onPrev: () => void;
addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
// Типы страховых случаев из erv_ticket
const EVENT_TYPES = [
{ value: 'delay_flight', label: 'Задержка авиарейса (более 3 часов)' },
{ value: 'cancel_flight', label: 'Отмена авиарейса' },
{ value: 'miss_connection', label: 'Пропуск (задержка прибытия) стыковочного рейса (авиа/жд/паром и тд)' },
{ value: 'emergency_landing', label: 'Посадка воздушного судна на запасной аэродром' },
{ value: 'delay_train', label: 'Задержка отправки поезда' },
{ value: 'cancel_train', label: 'Отмена поезда' },
{ value: 'delay_ferry', label: 'Задержка/отмена отправки парома/круизного судна' },
];
export default function Step2Details({ formData, updateFormData, onNext, onPrev, addDebugEvent }: Props) {
const [form] = Form.useForm();
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState('');
const handleNext = async () => {
try {
const values = await form.validateFields();
// Если есть файлы - загружаем
if (fileList.length > 0) {
setUploading(true);
setUploadProgress('📤 Подготавливаем документы...');
addDebugEvent?.('upload', 'pending', `📤 Загружаю ${fileList.length} документ(ов) в S3 через n8n...`, {
count: fileList.length
});
// Используем claim_id из formData (уже сгенерирован в Step1)
const claimId = formData.claim_id;
// Загружаем каждый документ через n8n вебхук
const uploadedFiles = [];
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
if (!file.originFileObj) continue;
setUploadProgress(`📡 Загружаем документ ${i + 1} из ${fileList.length}: ${file.name}...`);
const uploadFormData = new FormData();
uploadFormData.append('claim_id', claimId);
uploadFormData.append('file_type', `document_${i + 1}`); // document_1, document_2, etc
uploadFormData.append('filename', file.name);
uploadFormData.append('voucher', formData.voucher || '');
uploadFormData.append('session_id', sessionStorage.getItem('session_id') || 'unknown');
uploadFormData.append('upload_timestamp', new Date().toISOString());
uploadFormData.append('file', file.originFileObj);
const uploadResponse = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', {
method: 'POST',
body: uploadFormData,
});
setUploadProgress(`🔍 Обрабатываем документ ${i + 1} из ${fileList.length}...`);
const uploadResult = await uploadResponse.json();
const resultData = Array.isArray(uploadResult) ? uploadResult[0] : uploadResult;
if (resultData?.success) {
uploadedFiles.push({
filename: file.name,
success: true
});
}
}
const uploadResult = {
success: uploadedFiles.length > 0,
uploaded_count: uploadedFiles.length,
total_count: fileList.length,
files: uploadedFiles
};
if (uploadResult.success) {
addDebugEvent?.('upload', 'success', `✅ Документы загружены через n8n: ${uploadResult.uploaded_count}/${uploadResult.total_count}`, {
files: uploadResult.files,
claim_id: claimId
});
updateFormData({
...values,
uploadedFiles: uploadResult.files
});
} else {
message.error('Ошибка загрузки документов');
setUploading(false);
setUploadProgress('');
return;
}
setUploading(false);
setUploadProgress('');
} else {
updateFormData(values);
}
onNext();
} catch (error) {
message.error('Заполните все обязательные поля');
setUploading(false);
setUploadProgress('');
}
};
const handleUploadChange = ({ fileList: newFileList }: any) => {
setFileList(newFileList);
};
const [eventType, setEventType] = useState(formData.eventType || '');
const handleEventTypeChange = (value: string) => {
setEventType(value);
form.setFieldValue('eventType', value);
};
// Проверяем нужны ли дополнительные поля для стыковочного рейса
const showConnectionFields = eventType === 'miss_connection';
const showCancelFlightDocs = eventType === 'cancel_flight';
return (
<Form
form={form}
layout="vertical"
initialValues={formData}
style={{ marginTop: 24 }}
>
<Form.Item
label="Выберите тип события"
name="eventType"
rules={[{ required: true, message: 'Выберите тип события' }]}
>
<Select
placeholder="Выберите тип события"
size="large"
onChange={handleEventTypeChange}
>
{EVENT_TYPES.map(type => (
<Option key={type.value} value={type.value}>
{type.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="Дата наступления страхового случая"
name="incidentDate"
rules={[{ required: true, message: 'Укажите дату' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
{/* Для стыковочного рейса - номер рейса прибытия */}
{showConnectionFields && (
<Form.Item
label="Укажите номер рейса прибытия"
name="arrivalFlightNumber"
rules={[{ required: true, message: 'Введите номер рейса прибытия' }]}
>
<Input
placeholder="Введите номер"
size="large"
/>
</Form.Item>
)}
{showConnectionFields && (
<Form.Item
label="Дата рейса прибытия"
name="arrivalFlightDate"
rules={[{ required: true, message: 'Укажите дату прибытия' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
)}
{/* Для стыковочного рейса - номер рейса отправления */}
{showConnectionFields && (
<Form.Item
label="Укажите номер рейса отправления"
name="departureFlightNumber"
rules={[{ required: true, message: 'Введите номер рейса отправления' }]}
>
<Input
placeholder="Введите номер рейса отправления"
size="large"
/>
</Form.Item>
)}
{showConnectionFields && (
<Form.Item
label="Дата рейса отправления"
name="departureFlightDate"
rules={[{ required: true, message: 'Укажите дату отправления' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
)}
{/* Для обычных рейсов */}
{!showConnectionFields && (
<Form.Item
label="Номер рейса/поезда/парома"
name="transportNumber"
rules={[{ required: true, message: 'Введите номер' }]}
>
<Input
placeholder="Введите номер"
size="large"
/>
</Form.Item>
)}
{/* Дополнительные документы для отмены рейса */}
{showCancelFlightDocs && (
<Form.Item
label="Подтверждение уведомления об отмене рейса от АК"
name="cancelConfirmation"
tooltip="Уведомление от авиакомпании об отмене"
>
<Upload
listType="picture"
beforeUpload={(file) => {
const isLt15M = file.size / 1024 / 1024 < 15;
if (!isLt15M) {
message.error(`${file.name}: файл больше 15MB`);
return Upload.LIST_IGNORE;
}
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'application/pdf'];
const validExtensions = /\.(jpg|jpeg|png|pdf|heic|heif|webp)$/i;
if (!validTypes.includes(file.type) && !validExtensions.test(file.name)) {
message.error(`${file.name}: неподдерживаемый формат`);
return Upload.LIST_IGNORE;
}
return false;
}}
accept="image/*,.pdf,.heic,.heif,.webp"
multiple
maxCount={5}
>
<Button icon={<UploadOutlined />} size="large" block>
Загрузить подтверждение отмены
</Button>
</Upload>
</Form.Item>
)}
<Form.Item
label="Подтверждающие документы"
name="documents"
tooltip="Посадочный талон, билет, справка о задержке и т.д."
>
<Upload
listType="picture"
fileList={fileList}
onChange={handleUploadChange}
beforeUpload={(file) => {
const isLt15M = file.size / 1024 / 1024 < 15;
if (!isLt15M) {
message.error(`${file.name}: файл больше 15MB`);
return Upload.LIST_IGNORE;
}
if (fileList.length >= 10) {
message.error('Максимум 10 файлов');
return Upload.LIST_IGNORE;
}
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'application/pdf'];
const validExtensions = /\.(jpg|jpeg|png|pdf|heic|heif|webp)$/i;
if (!validTypes.includes(file.type) && !validExtensions.test(file.name)) {
message.error(`${file.name}: неподдерживаемый формат`);
return Upload.LIST_IGNORE;
}
return false;
}}
accept="image/*,.pdf,.heic,.heif,.webp"
multiple
maxCount={10}
showUploadList={{
showPreviewIcon: true,
showRemoveIcon: true,
}}
>
<Button icon={<UploadOutlined />} size="large" block disabled={fileList.length >= 10}>
Загрузить файлы (до 10 шт, макс 15MB каждый)
</Button>
</Upload>
<div style={{ marginTop: 8, fontSize: 12, color: '#999' }}>
Загружено: {fileList.length}/10 файлов
</div>
</Form.Item>
{/* Прогресс обработки */}
{uploading && uploadProgress && (
<Alert
message={uploadProgress}
type="info"
showIcon
icon={<Spin indicator={<LoadingOutlined style={{ fontSize: 16 }} spin />} />}
style={{ marginBottom: 16, marginTop: 16 }}
/>
)}
<Form.Item>
<div style={{ display: 'flex', gap: 8, marginTop: 32 }}>
<Button onClick={onPrev} size="large" disabled={uploading}>Назад</Button>
<Button
type="primary"
onClick={handleNext}
loading={uploading}
style={{ flex: 1 }}
size="large"
>
{uploading ? 'Обрабатываем...' : 'Далее'}
</Button>
</div>
</Form.Item>
{/* 🔧 Технические кнопки для разработки */}
<div style={{
marginTop: 24,
padding: 16,
background: '#f0f0f0',
borderRadius: 8,
border: '2px dashed #999'
}}>
<div style={{ marginBottom: 8, fontSize: 12, color: '#666', fontWeight: 'bold' }}>
🔧 DEV MODE - Быстрая навигация (без валидации)
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button
onClick={onPrev}
size="small"
disabled={uploading}
>
Назад (Step 1)
</Button>
<Button
type="dashed"
onClick={() => {
// Пропускаем валидацию, заполняем минимальные данные
const devData = {
eventType: 'delay_flight',
incidentDate: dayjs(),
transportNumber: 'TEST123',
};
updateFormData(devData);
onNext();
}}
size="small"
style={{ flex: 1 }}
>
Далее (Step 3) [пропустить]
</Button>
</div>
</div>
</Form>
);
}