diff --git a/frontend/src/components/form/Step1Policy.tsx b/frontend/src/components/form/Step1Policy.tsx
index cad1043..8be77cd 100644
--- a/frontend/src/components/form/Step1Policy.tsx
+++ b/frontend/src/components/form/Step1Policy.tsx
@@ -622,6 +622,168 @@ export default function Step1Policy({ formData, updateFormData, onNext, addDebug
) : null}
+ {/* 🔧 Технические кнопки для разработки */}
+
+
+ 🔧 DEV MODE - Быстрая навигация (без валидации)
+
+
+
+
+
+
+ );
+}
+
+ )}
+
+
+
+ {/* Прогресс обработки */}
+ {uploading && uploadProgress && (
+ } />}
+ style={{ marginBottom: 16 }}
+ />
+ )}
+
+
+
+
+
+
+
+ >
+ )}
+
+ {!policyNotFound && (
+
+
+ 💡 Введите номер полиса. Кириллица автоматически заменяется на латиницу, тире вставляется автоматически
+
+
+ )}
+
+ {/* Модальное окно ожидания OCR результата */}
+ {
+ setOcrModalVisible(false);
+ onNext(); // Переход на следующий шаг
+ }}>
+ Продолжить →
+
+ ] : [
+ // ❌ Полис не распознан - кнопка "Загрузить другой файл"
+
+ ]
+ }
+ width={700}
+ centered
+ >
+ {ocrModalContent === 'loading' ? (
+
+
} />
+
⏳ Обрабатываем документ
+
OCR распознавание текста...
+
AI анализ содержимого...
+
Проверка валидности полиса...
+
+ Это может занять 20-30 секунд. Пожалуйста, подождите...
+
+
+ ) : ocrModalContent ? (
+
+
+ {ocrModalContent.success ? '✅ Результат распознавания' : '❌ Ошибка распознавания'}
+
+ {ocrModalContent.success ? (
+
+
Номер полиса: {ocrModalContent.data?.policy_number || 'н/д'}
+
Владелец: {ocrModalContent.data?.policyholder_full_name || 'н/д'}
+ {ocrModalContent.data?.insured_persons?.length > 0 && (
+ <>
+
Застрахованные лица:
+
+ {ocrModalContent.data.insured_persons.map((person: any, i: number) => (
+ - {person.full_name} (ДР: {person.birth_date || 'н/д'})
+ ))}
+
+ >
+ )}
+ {ocrModalContent.data?.policy_period && (
+
Период: {ocrModalContent.data.policy_period.insured_from} - {ocrModalContent.data.policy_period.insured_to}
+ )}
+
Полный ответ AI:
+
+ {JSON.stringify(ocrModalContent.data, null, 2)}
+
+
+ ) : (
+
+
{ocrModalContent.message || 'Документ не распознан'}
+
Полный ответ:
+
+ {JSON.stringify(ocrModalContent.data, null, 2)}
+
+
+ )}
+
+ ) : null}
+
+
{/* 🔧 Технические кнопки для разработки */}
= {
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 [currentDocumentIndex, setCurrentDocumentIndex] = useState(0);
+ const [processedDocuments, setProcessedDocuments] = useState>({});
+ const [currentFile, setCurrentFile] = useState(null);
const [uploading, setUploading] = useState(false);
- const [uploadProgress, setUploadProgress] = useState('');
- const [waitingForOcr, setWaitingForOcr] = useState(false);
- const [ocrResults, setOcrResults] = useState(null);
+ const [ocrModalVisible, setOcrModalVisible] = useState(false);
+ const [ocrModalContent, setOcrModalContent] = useState(null);
const eventSourceRef = useRef(null);
const handleEventTypeChange = (value: string) => {
setEventType(value);
- setDocumentFiles({}); // Очищаем загруженные файлы при смене типа
+ setCurrentDocumentIndex(0);
+ setProcessedDocuments({});
+ setCurrentFile(null);
form.setFieldValue('eventType', value);
};
// Получаем конфигурацию документов для выбранного типа события
const currentDocuments = eventType ? DOCUMENT_CONFIGS[eventType] || [] : [];
+ const currentDocConfig = currentDocuments[currentDocumentIndex];
+
+ // Проверяем все ли обязательные документы обработаны
+ const allRequiredProcessed = currentDocuments
+ .filter(doc => doc.required)
+ .every(doc => processedDocuments[doc.field]);
- const handleUploadChange = (field: string, { fileList: newFileList }: any) => {
- setDocumentFiles(prev => ({
- ...prev,
- [field]: newFileList
- }));
+ // SSE подключение для получения результатов OCR
+ useEffect(() => {
+ const claimId = formData.claim_id;
+ if (!claimId || !uploading || !currentDocConfig) {
+ return;
+ }
+
+ console.log('🔌 SSE: Открываю соединение для', currentDocConfig.file_type);
+
+ const eventSource = new EventSource(`/events/${claimId}`);
+ eventSourceRef.current = eventSource;
+
+ const expectedEventType = `${currentDocConfig.file_type}_processed`;
+ console.log('👀 Ожидаю event_type:', expectedEventType);
+
+ eventSource.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+ console.log('📨 SSE event received:', data);
+
+ if (data.event_type === expectedEventType) {
+ console.log('✅ Получил результат для документа:', currentDocConfig.name);
+
+ // Сохраняем результат
+ setProcessedDocuments(prev => ({
+ ...prev,
+ [currentDocConfig.field]: data.data?.output || data.data
+ }));
+
+ // Показываем результат в модалке
+ setOcrModalContent({
+ success: data.status === 'completed',
+ data: data.data?.output || data.data,
+ message: data.message,
+ documentName: currentDocConfig.name
+ });
+
+ setUploading(false);
+ eventSource.close();
+
+ addDebugEvent?.('ocr', 'success', `✅ ${currentDocConfig.name} обработан`, {
+ file_type: currentDocConfig.file_type,
+ data: data.data?.output || data.data
+ });
+ }
+ } catch (error) {
+ console.error('SSE parse error:', error);
+ }
+ };
+
+ eventSource.onerror = (error) => {
+ console.error('❌ SSE connection error:', error);
+
+ setOcrModalContent((prev) => {
+ if (prev && prev !== 'loading') {
+ console.log('✅ SSE закрыто после получения результата');
+ return prev;
+ }
+ return { success: false, data: null, message: 'Ошибка подключения к серверу' };
+ });
+
+ setUploading(false);
+ eventSource.close();
+ };
+
+ eventSource.onopen = () => {
+ console.log('✅ SSE: Соединение открыто');
+ };
+
+ return () => {
+ if (eventSourceRef.current) {
+ eventSourceRef.current.close();
+ eventSourceRef.current = null;
+ }
+ };
+ }, [formData.claim_id, uploading, currentDocConfig]);
+
+ const handleFileSelect = (file: File) => {
+ setCurrentFile(file);
+ return false; // Предотвращаем автозагрузку
};
- const handleNext = async () => {
+ const handleUploadAndProcess = async () => {
+ if (!currentFile || !currentDocConfig) {
+ message.error('Выберите файл');
+ return;
+ }
+
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('📤 Загружаем документы...');
-
+ setOcrModalVisible(true);
+ setOcrModalContent('loading');
+
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('Заполните все обязательные поля');
+ addDebugEvent?.('upload', 'pending', `📤 Загружаю: ${currentDocConfig.name}`, {
+ file_type: currentDocConfig.file_type,
+ filename: currentFile.name
+ });
+
+ const uploadFormData = new FormData();
+ uploadFormData.append('claim_id', claimId);
+ uploadFormData.append('file_type', currentDocConfig.file_type);
+ uploadFormData.append('filename', currentFile.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', currentFile);
+
+ const uploadResponse = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', {
+ method: 'POST',
+ body: uploadFormData,
+ });
+
+ const uploadResult = await uploadResponse.json();
+ console.log('📤 Файл загружен, ждём OCR результат...');
+
+ addDebugEvent?.('upload', 'success', `✅ Файл загружен, обрабатывается...`, {
+ file_type: currentDocConfig.file_type
+ });
+
+ // SSE обработчик получит результат и обновит состояние
+
+ } catch (error: any) {
+ console.error('Ошибка загрузки:', error);
+ message.error('Ошибка загрузки файла');
setUploading(false);
- setUploadProgress('');
+ setOcrModalVisible(false);
+
+ addDebugEvent?.('upload', 'error', `❌ Ошибка загрузки: ${error.message}`);
}
};
+ const handleContinueToNextDocument = () => {
+ setOcrModalVisible(false);
+ setCurrentFile(null);
+ setCurrentDocumentIndex(prev => prev + 1);
+ };
+
+ const handleSkipOptionalDocument = () => {
+ setCurrentFile(null);
+ setCurrentDocumentIndex(prev => prev + 1);
+ };
+
+ const handleFinishStep = () => {
+ updateFormData({
+ eventType,
+ processedDocuments
+ });
+ onNext();
+ };
+
+ // Прогресс загрузки
+ const totalRequired = currentDocuments.filter(d => d.required).length;
+ const processedRequired = currentDocuments.filter(d => d.required && processedDocuments[d.field]).length;
+ const progressPercent = totalRequired > 0 ? Math.round((processedRequired / totalRequired) * 100) : 0;
+
return (