import { Upload, Button, Card, Alert, Modal, Spin, Progress, message } from 'antd'; import { UploadOutlined, FileTextOutlined, CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons'; import { useState, useEffect, useRef } from 'react'; import type { UploadFile } from 'antd/es/upload/interface'; interface DocumentConfig { name: string; field: string; file_type: string; required: boolean; maxFiles: number; description: string; } interface Props { documentConfig: DocumentConfig; formData: any; updateFormData: (data: any) => void; onNext: () => void; onPrev: () => void; isLastDocument: boolean; currentDocNumber: number; totalDocs: number; } const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://147.45.146.17:8100'; const StepDocumentUpload: React.FC = ({ documentConfig, formData, updateFormData, onNext, onPrev, isLastDocument, currentDocNumber, totalDocs }) => { const [fileList, setFileList] = useState([]); const [uploading, setUploading] = useState(false); const [processingModalVisible, setProcessingModalVisible] = useState(false); const [processingModalContent, setProcessingModalContent] = useState('loading'); const eventSourceRef = useRef(null); const claimId = formData.claim_id; const sessionId = formData.session_id || `sess-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // Проверяем, загружен ли уже документ const documentData = formData.documents?.[documentConfig.file_type]; const isAlreadyUploaded = documentData?.uploaded; useEffect(() => { // Если документ уже загружен, можно сразу показать кнопку "Продолжить" if (isAlreadyUploaded) { console.log(`✅ Документ ${documentConfig.file_type} уже загружен`); } }, [isAlreadyUploaded, documentConfig.file_type]); const handleUpload = async () => { console.log('🚀 handleUpload called', { fileListLength: fileList.length }); if (fileList.length === 0) { message.error('Пожалуйста, выберите файл для загрузки'); return; } setUploading(true); try { // Берём первый файл (у нас только один файл на шаг) const file = fileList[0]; if (!file.originFileObj) { message.error('Ошибка: файл не найден'); setUploading(false); return; } console.log('📎 File:', file.name, file.originFileObj); const formDataToSend = new FormData(); formDataToSend.append('claim_id', claimId); formDataToSend.append('file_type', documentConfig.file_type); formDataToSend.append('filename', file.name); // Оригинальное имя файла formDataToSend.append('voucher', formData.voucher || ''); formDataToSend.append('session_id', sessionId); formDataToSend.append('upload_timestamp', new Date().toISOString()); formDataToSend.append('file', file.originFileObj); // 'file' - единственное число! console.log('📤 Uploading to n8n:', { claim_id: claimId, session_id: sessionId, file_type: documentConfig.file_type, filename: file.name, voucher: formData.voucher, upload_timestamp: new Date().toISOString() }); // Показываем модалку обработки setProcessingModalVisible(true); setProcessingModalContent('loading'); // Подключаемся к SSE для получения результата const event_type = `${documentConfig.file_type}_processed`; const eventSource = new EventSource( `${API_BASE_URL}/events/${claimId}?event_type=${event_type}` ); eventSourceRef.current = eventSource; eventSource.onmessage = (event) => { console.log('📨 SSE message received:', event.data); try { const result = JSON.parse(event.data); console.log('📦 Parsed result:', result); if (result.event_type === event_type) { setProcessingModalContent(result); // Сохраняем данные документа в formData const updatedDocuments = { ...(formData.documents || {}), [documentConfig.file_type]: { uploaded: true, data: result.data, file_type: documentConfig.file_type } }; updateFormData({ documents: updatedDocuments, session_id: sessionId }); } } catch (error) { console.error('❌ Error parsing SSE message:', error); } }; eventSource.onerror = (error) => { console.log('🔌 SSE connection closed'); setProcessingModalContent((prev: any) => { if (prev && prev !== 'loading') { console.log('✅ SSE закрыто после получения результата - всё ОК'); return prev; // Оставляем полученный результат } // Реальная ошибка - не получили данные console.error('❌ SSE ошибка: не получили данные', error); return { success: false, message: 'Ошибка подключения к серверу', data: null }; }); setUploading(false); eventSource.close(); }; // Отправляем файл на сервер через backend API (proxy к n8n) const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8100'; const response = await fetch(`${API_BASE_URL}/api/n8n/upload/file`, { method: 'POST', body: formDataToSend, }); if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`); } console.log('✅ File uploaded successfully'); } catch (error) { console.error('❌ Upload error:', error); message.error('Ошибка загрузки файла'); setUploading(false); setProcessingModalVisible(false); if (eventSourceRef.current) { eventSourceRef.current.close(); } } }; const handleContinue = () => { setProcessingModalVisible(false); setUploading(false); if (eventSourceRef.current) { eventSourceRef.current.close(); } onNext(); }; const handleSkipDocument = () => { if (documentConfig.required) { message.warning('Этот документ обязателен для загрузки'); return; } // Пропускаем необязательный документ const updatedDocuments = { ...(formData.documents || {}), [documentConfig.file_type]: { uploaded: false, skipped: true, data: null } }; updateFormData({ documents: updatedDocuments }); onNext(); }; return (
{/* Прогресс */}
Документ {currentDocNumber} из {totalDocs} {Math.round(((currentDocNumber - 1) / totalDocs) * 100)}% завершено
{/* Заголовок */}

{documentConfig.name} {documentConfig.required && *}

{documentConfig.description}

{!documentConfig.required && (

⚠️ Этот документ необязателен, можно пропустить

)}
{/* Если документ уже загружен */} {isAlreadyUploaded && ( )} {/* Загрузка файла */} { console.log('📁 Upload onChange:', newFileList?.length, 'files'); setFileList(newFileList || []); }} beforeUpload={() => false} maxCount={documentConfig.maxFiles} accept="image/*,application/pdf" listType="picture" disabled={uploading} >

Поддерживаются: JPG, PNG, PDF (до 10 МБ)

{/* Кнопки */}
{isAlreadyUploaded ? ( ) : ( <> {!documentConfig.required && ( )} )}
{/* 🔧 DEV MODE */}
🔧 DEV MODE - Быстрая навигация
{/* Модалка обработки */} { if (processingModalContent !== 'loading') { setProcessingModalVisible(false); setUploading(false); if (eventSourceRef.current) { eventSourceRef.current.close(); } } }} footer={processingModalContent === 'loading' ? null : [ ]} closable={processingModalContent !== 'loading'} maskClosable={false} > {processingModalContent === 'loading' ? (
} />

Обрабатываем документ...

Извлекаем данные с помощью AI

) : (
Документ обработан } description={processingModalContent.message || 'Данные успешно извлечены'} type="success" showIcon={false} style={{ marginBottom: 16 }} />
                {JSON.stringify(processingModalContent.data?.output || processingModalContent.data, null, 2)}
              
)}
); }; export default StepDocumentUpload;