import { Form, Button, Select, Upload, message, Spin, Alert, Card } from 'antd'; import { UploadOutlined, LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { useState, useEffect, useRef } 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; } // Типы страховых случаев 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: 'Задержка/отмена отправки парома/круизного судна' }, ]; // Конфигурация документов для каждого типа события с уникальными file_type const DOCUMENT_CONFIGS: Record = { delay_flight: [ { name: "Посадочный талон или Билет", field: "boarding_or_ticket", file_type: "flight_delay_boarding_or_ticket", required: true, maxFiles: 1, description: "Boarding pass или ticket/booking confirmation" }, { name: "Подтверждение задержки", field: "delay_confirmation", file_type: "flight_delay_confirmation", required: true, maxFiles: 3, description: "Справка от АК, email/SMS, или фото табло" } ], cancel_flight: [ { name: "Билет", field: "ticket", file_type: "flight_cancel_ticket", required: true, maxFiles: 1, description: "Ticket/booking confirmation" }, { name: "Уведомление об отмене", field: "cancellation_notice", file_type: "flight_cancel_notice", required: true, maxFiles: 3, description: "Email, SMS или скриншот из приложения АК" } ], miss_connection: [ { name: "Посадочный талон рейса ПРИБЫТИЯ", field: "arrival_boarding", file_type: "connection_arrival_boarding", required: true, maxFiles: 1, description: "Boarding pass рейса, который задержался" }, { name: "Посадочный талон ИЛИ Билет рейса ОТПРАВЛЕНИЯ", field: "departure_boarding_or_ticket", file_type: "connection_departure_boarding_or_ticket", required: true, maxFiles: 1, description: "Boarding pass (если успели) ИЛИ билет (если не успели)" }, { name: "Доказательство задержки (опционально)", field: "delay_proof", file_type: "connection_delay_proof", required: false, maxFiles: 5, description: "Справка, фото табло, email/SMS" } ], delay_train: [ { name: "Билет на поезд", field: "train_ticket", file_type: "train_ticket", required: true, maxFiles: 1, description: "Билет РЖД или другого перевозчика" }, { name: "Подтверждение задержки", field: "delay_proof", file_type: "train_delay_proof", required: true, maxFiles: 3, description: "Справка от РЖД, фото табло, скриншот приложения" } ], cancel_train: [ { name: "Билет на поезд", field: "train_ticket", file_type: "train_ticket", required: true, maxFiles: 1, description: "Билет РЖД или другого перевозчика" }, { name: "Подтверждение отмены", field: "cancel_proof", file_type: "train_cancel_proof", required: true, maxFiles: 3, description: "Справка от РЖД, фото табло, скриншот приложения" } ], delay_ferry: [ { name: "Билет на паром/круиз", field: "ferry_ticket", file_type: "ferry_ticket", required: true, maxFiles: 1, description: "Билет или booking confirmation" }, { name: "Подтверждение задержки/отмены", field: "delay_proof", file_type: "ferry_delay_proof", required: true, maxFiles: 3, description: "Справка от перевозчика, фото расписания, email/SMS" } ], emergency_landing: [ { name: "Посадочный талон или Билет", field: "boarding_or_ticket", file_type: "emergency_boarding_or_ticket", required: true, maxFiles: 1, description: "Boarding pass или ticket" }, { name: "Подтверждение посадки на запасной аэродром", field: "emergency_proof", file_type: "emergency_landing_proof", required: true, maxFiles: 3, description: "Справка от АК, email/SMS, документы" } ] }; export default function Step2Details({ formData, updateFormData, onNext, onPrev, addDebugEvent }: Props) { const [form] = Form.useForm(); const [eventType, setEventType] = useState(formData.eventType || ''); const [documentFiles, setDocumentFiles] = useState>({}); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(''); const [waitingForOcr, setWaitingForOcr] = useState(false); const [ocrResults, setOcrResults] = useState(null); const eventSourceRef = useRef(null); const handleEventTypeChange = (value: string) => { setEventType(value); setDocumentFiles({}); // Очищаем загруженные файлы при смене типа form.setFieldValue('eventType', value); }; // Получаем конфигурацию документов для выбранного типа события const currentDocuments = eventType ? DOCUMENT_CONFIGS[eventType] || [] : []; const handleUploadChange = (field: string, { fileList: newFileList }: any) => { setDocumentFiles(prev => ({ ...prev, [field]: newFileList })); }; const handleNext = async () => { try { const values = await form.validateFields(); // Проверяем что все обязательные документы загружены const missingDocs = currentDocuments.filter(doc => doc.required && (!documentFiles[doc.field] || documentFiles[doc.field].length === 0) ); if (missingDocs.length > 0) { message.error(`Загрузите обязательные документы: ${missingDocs.map(d => d.name).join(', ')}`); return; } // Загружаем все документы в S3 через n8n setUploading(true); setUploadProgress('📤 Загружаем документы...'); const claimId = formData.claim_id; const uploadedFiles: any[] = []; for (const docConfig of currentDocuments) { const files = documentFiles[docConfig.field] || []; for (let i = 0; i < files.length; i++) { const file = files[i]; if (!file.originFileObj) continue; setUploadProgress(`📡 Загружаем: ${docConfig.name} (${i + 1}/${files.length})...`); const uploadFormData = new FormData(); uploadFormData.append('claim_id', claimId); uploadFormData.append('file_type', docConfig.file_type); // 🔑 Уникальный file_type для n8n 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); addDebugEvent?.('upload', 'pending', `📤 Загружаю документ: ${docConfig.name} (${docConfig.file_type})`, { file_type: docConfig.file_type, filename: file.name }); const uploadResponse = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', { method: 'POST', body: uploadFormData, }); const uploadResult = await uploadResponse.json(); const resultData = Array.isArray(uploadResult) ? uploadResult[0] : uploadResult; if (resultData?.success) { uploadedFiles.push({ filename: file.name, file_type: docConfig.file_type, field: docConfig.field, success: true }); addDebugEvent?.('upload', 'success', `✅ Документ загружен: ${docConfig.name}`, { file_type: docConfig.file_type, filename: file.name }); } } } if (uploadedFiles.length > 0) { setUploadProgress('🤖 AI анализирует документы...'); updateFormData({ ...values, uploadedDocuments: uploadedFiles }); // TODO: Здесь будет ожидание SSE события с результатами OCR/AI // Пока просто переходим дальше setUploadProgress(''); setUploading(false); message.success(`Загружено документов: ${uploadedFiles.length}. Переходим дальше...`); onNext(); } else { message.error('Не удалось загрузить документы'); setUploading(false); setUploadProgress(''); } } catch (error) { message.error('Заполните все обязательные поля'); setUploading(false); setUploadProgress(''); } }; return (
{/* Показываем документы только после выбора типа события */} {eventType && currentDocuments.length > 0 && (

💡 Просто загрузите документы — наш AI автоматически распознает все данные (номера рейсов, даты, время, причины задержек)

{currentDocuments.map((doc, index) => (
{doc.required ? '✅' : 'ℹ️'} {doc.name} {doc.required && *}
💡 {doc.description}
handleUploadChange(doc.field, info)} beforeUpload={(file) => { const isLt15M = file.size / 1024 / 1024 < 15; if (!isLt15M) { message.error(`${file.name}: файл больше 15MB`); return Upload.LIST_IGNORE; } const currentFiles = documentFiles[doc.field] || []; if (currentFiles.length >= doc.maxFiles) { message.error(`Максимум ${doc.maxFiles} файл(ов) для этого документа`); 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={doc.maxFiles > 1} maxCount={doc.maxFiles} showUploadList={{ showPreviewIcon: true, showRemoveIcon: true, }} >
Загружено: {(documentFiles[doc.field] || []).length}/{doc.maxFiles} файл(ов)
))}
)} {/* Прогресс обработки */} {uploading && uploadProgress && ( } />} style={{ marginBottom: 16, marginTop: 16 }} /> )}
{/* 🔧 Технические кнопки для разработки */}
🔧 DEV MODE - Быстрая навигация (без валидации)
); }