diff --git a/frontend/src/components/DebugPanel.tsx b/frontend/src/components/DebugPanel.tsx
new file mode 100644
index 0000000..247918d
--- /dev/null
+++ b/frontend/src/components/DebugPanel.tsx
@@ -0,0 +1,236 @@
+import { Card, Timeline, Tag, Descriptions } from 'antd';
+import { CheckCircleOutlined, LoadingOutlined, ExclamationCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
+
+interface DebugEvent {
+ timestamp: string;
+ type: 'policy_check' | 'ocr' | 'ai_analysis' | 'upload' | 'error';
+ status: 'pending' | 'success' | 'warning' | 'error';
+ message: string;
+ data?: any;
+}
+
+interface Props {
+ events: DebugEvent[];
+ formData: any;
+}
+
+export default function DebugPanel({ events, formData }: Props) {
+ const getIcon = (status: string) => {
+ switch (status) {
+ case 'success': return ;
+ case 'pending': return ;
+ case 'warning': return ;
+ case 'error': return ;
+ default: return ;
+ }
+ };
+
+ const getColor = (status: string) => {
+ switch (status) {
+ case 'success': return 'success';
+ case 'pending': return 'processing';
+ case 'warning': return 'warning';
+ case 'error': return 'error';
+ default: return 'default';
+ }
+ };
+
+ return (
+
+
+ {/* Текущие данные формы */}
+
+
+ Form Data:
+
+
+ {JSON.stringify(formData, null, 2)}
+
+
+
+ {/* События */}
+
+ Events Log:
+
+
+
+ {events.length === 0 && (
+
+ Нет событий...
+
+ )}
+
+ {events.map((event, index) => (
+
+
+
+ {event.timestamp}
+
+
+
+ {event.type.toUpperCase()}
+
+ {event.message}
+
+
+ {event.data && (
+
+ {event.type === 'policy_check' && event.data.found !== undefined && (
+
+ Found}
+ labelStyle={{ background: '#252526', color: '#9cdcfe' }}
+ contentStyle={{ background: '#1e1e1e', color: event.data.found ? '#4ec9b0' : '#f48771' }}
+ >
+ {event.data.found ? 'TRUE' : 'FALSE'}
+
+ {event.data.holder_name && (
+ Holder}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#ce9178' }}
+ >
+ {event.data.holder_name}
+
+ )}
+
+ )}
+
+ {event.type === 'ocr' && (
+
+ {event.data.text?.substring(0, 300)}...
+
+ )}
+
+ {event.type === 'ai_analysis' && (
+
+ Type}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#4ec9b0' }}
+ >
+ {event.data.document_type}
+
+ Valid}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: event.data.is_valid ? '#4ec9b0' : '#f48771' }}
+ >
+ {event.data.is_valid ? 'TRUE' : 'FALSE'}
+
+ Confidence}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
+ >
+ {(event.data.confidence * 100).toFixed(0)}%
+
+ {event.data.extracted_data && Object.keys(event.data.extracted_data).length > 0 && (
+ Extracted}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e' }}
+ >
+
+ {JSON.stringify(event.data.extracted_data, null, 2)}
+
+
+ )}
+
+ )}
+
+ {event.type === 'upload' && (
+
+ File ID}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#569cd6', fontSize: 10 }}
+ >
+ {event.data.file_id}
+
+ Size}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
+ >
+ {(event.data.size / 1024).toFixed(1)} KB
+
+ {event.data.url && (
+ S3 URL}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', fontSize: 9 }}
+ >
+
+ {event.data.url.substring(0, 50)}...
+
+
+ )}
+
+ )}
+
+ )}
+
+
+ ))}
+
+
+ {events.length > 0 && (
+
+
+ Total events: {events.length}
+
+
+ )}
+
+
+ );
+}
+
diff --git a/frontend/src/components/form/Step1Policy.tsx b/frontend/src/components/form/Step1Policy.tsx
index e32f6eb..22a75e8 100644
--- a/frontend/src/components/form/Step1Policy.tsx
+++ b/frontend/src/components/form/Step1Policy.tsx
@@ -7,6 +7,7 @@ interface Props {
formData: any;
updateFormData: (data: any) => void;
onNext: () => void;
+ addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
// Расширенная функция автозамены кириллицы на латиницу
@@ -49,7 +50,7 @@ const formatVoucher = (value: string): string => {
}
};
-export default function Step1Policy({ formData, updateFormData, onNext }: Props) {
+export default function Step1Policy({ formData, updateFormData, onNext, addDebugEvent }: Props) {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [policyNotFound, setPolicyNotFound] = useState(false);
@@ -77,6 +78,8 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
setLoading(true);
setPolicyNotFound(false);
+ addDebugEvent?.('policy_check', 'pending', `Проверяю полис: ${values.voucher}`, { voucher: values.voucher });
+
// Проверка полиса через API
const response = await fetch('http://147.45.146.17:8100/api/v1/policy/check', {
method: 'POST',
@@ -92,15 +95,24 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
if (response.ok) {
if (result.found) {
// Полис найден - переходим дальше
+ addDebugEvent?.('policy_check', 'success', `✅ Полис найден в MySQL БД (33,963 полисов)`, {
+ found: true,
+ voucher: values.voucher
+ });
message.success('Полис найден в базе данных');
updateFormData(values);
onNext();
} else {
// Полис НЕ найден - показываем загрузку скана
+ addDebugEvent?.('policy_check', 'warning', `⚠️ Полис не найден → требуется загрузка скана`, {
+ found: false,
+ voucher: values.voucher
+ });
message.warning('Полис не найден в базе. Загрузите скан полиса');
setPolicyNotFound(true);
}
} else {
+ addDebugEvent?.('policy_check', 'error', `❌ Ошибка API: ${result.detail}`, { error: result.detail });
message.error(result.detail || 'Ошибка проверки полиса');
}
} catch (error: any) {
@@ -133,6 +145,10 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
setUploading(true);
const values = await form.validateFields(['voucher']);
+ addDebugEvent?.('upload', 'pending', `📤 Загружаю ${fileList.length} файл(ов) в S3...`, {
+ count: fileList.length
+ });
+
// Загружаем файлы в S3 с OCR проверкой
const formData = new FormData();
fileList.forEach((file: any) => {
@@ -150,19 +166,59 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
const uploadResult = await uploadResponse.json();
if (uploadResult.success) {
- // TODO: OCR проверка что это полис, а не шляпа
- // Если шляпа - помечаем себе, пользователю не говорим
+ addDebugEvent?.('upload', 'success', `✅ Загружено в S3: ${uploadResult.uploaded_count}/${uploadResult.total_count}`, {
+ uploaded_count: uploadResult.uploaded_count,
+ files: uploadResult.files
+ });
+
+ // Проверяем OCR результаты
+ if (uploadResult.files && uploadResult.files.length > 0) {
+ const firstFile = uploadResult.files[0];
+
+ addDebugEvent?.('ocr', 'pending', `🔍 Запущен OCR для: ${firstFile.filename}`, {
+ file_id: firstFile.file_id,
+ filename: firstFile.filename
+ });
+
+ // Если есть OCR результат
+ if (firstFile.ocr_result) {
+ const ocr = firstFile.ocr_result;
+
+ addDebugEvent?.('ocr', 'success', `📄 OCR завершен: ${ocr.ocr_text?.length || 0} символов`, {
+ text: ocr.ocr_text?.substring(0, 300)
+ });
+
+ if (ocr.ai_analysis) {
+ const isGarbage = ocr.document_type === 'garbage';
+
+ addDebugEvent?.(
+ 'ai_analysis',
+ isGarbage ? 'warning' : 'success',
+ isGarbage
+ ? `🗑️ ШЛЯПА DETECTED! (пользователю не говорим)`
+ : `🤖 AI: ${ocr.document_type}, confidence: ${(ocr.confidence * 100).toFixed(0)}%`,
+ {
+ document_type: ocr.document_type,
+ is_valid: ocr.is_valid,
+ confidence: ocr.confidence,
+ extracted_data: ocr.extracted_data
+ }
+ );
+ }
+ }
+ }
updateFormData({
...values,
policyScanUploaded: true,
policyScanFiles: uploadResult.files,
- policyValidationWarning: '' // TODO: OCR validation
+ policyValidationWarning: '' // Silent validation
});
message.success(`Загружено файлов: ${uploadResult.uploaded_count}`);
onNext();
} else {
+ addDebugEvent?.('upload', 'error', `❌ Ошибка загрузки файлов`, { error: 'Upload failed' });
message.error('Ошибка загрузки файлов');
}
} catch (error) {
diff --git a/frontend/src/components/form/Step3Payment.tsx b/frontend/src/components/form/Step3Payment.tsx
index 575a7f0..d160560 100644
--- a/frontend/src/components/form/Step3Payment.tsx
+++ b/frontend/src/components/form/Step3Payment.tsx
@@ -11,6 +11,7 @@ interface Props {
onSubmit: () => void;
isPhoneVerified: boolean;
setIsPhoneVerified: (verified: boolean) => void;
+ addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
export default function Step3Payment({
@@ -19,7 +20,8 @@ export default function Step3Payment({
onPrev,
onSubmit,
isPhoneVerified,
- setIsPhoneVerified
+ setIsPhoneVerified,
+ addDebugEvent
}: Props) {
const [form] = Form.useForm();
const [codeSent, setCodeSent] = useState(false);
@@ -36,6 +38,9 @@ export default function Step3Payment({
}
setLoading(true);
+
+ addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
+
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -45,12 +50,18 @@ export default function Step3Payment({
const result = await response.json();
if (response.ok) {
+ addDebugEvent?.('sms', 'success', `✅ SMS отправлен (DEBUG mode)`, {
+ phone,
+ debug_code: result.debug_code,
+ message: result.message
+ });
message.success('Код отправлен на ваш телефон');
setCodeSent(true);
if (result.debug_code) {
message.info(`DEBUG: Код ${result.debug_code}`);
}
} else {
+ addDebugEvent?.('sms', 'error', `❌ Ошибка SMS: ${result.detail}`, { error: result.detail });
message.error(result.detail || 'Ошибка отправки кода');
}
} catch (error) {
@@ -71,6 +82,9 @@ export default function Step3Payment({
}
setVerifyLoading(true);
+
+ addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
+
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -80,9 +94,18 @@ export default function Step3Payment({
const result = await response.json();
if (response.ok) {
+ addDebugEvent?.('sms', 'success', `✅ Телефон подтвержден успешно`, {
+ phone,
+ verified: true
+ });
message.success('Телефон подтвержден!');
setIsPhoneVerified(true);
} else {
+ addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, {
+ phone,
+ code,
+ error: result.detail
+ });
message.error(result.detail || 'Неверный код');
}
} catch (error) {
diff --git a/frontend/src/pages/ClaimForm.tsx b/frontend/src/pages/ClaimForm.tsx
index 69fa25c..f387709 100644
--- a/frontend/src/pages/ClaimForm.tsx
+++ b/frontend/src/pages/ClaimForm.tsx
@@ -1,8 +1,9 @@
import { useState } from 'react';
-import { Steps, Card, message } from 'antd';
+import { Steps, Card, message, Row, Col } from 'antd';
import Step1Policy from '../components/form/Step1Policy';
import Step2Details from '../components/form/Step2Details';
import Step3Payment from '../components/form/Step3Payment';
+import DebugPanel from '../components/DebugPanel';
import './ClaimForm.css';
const { Step } = Steps;
@@ -35,6 +36,18 @@ export default function ClaimForm() {
paymentMethod: 'sbp',
});
const [isPhoneVerified, setIsPhoneVerified] = useState(false);
+ const [debugEvents, setDebugEvents] = useState([]);
+
+ const addDebugEvent = (type: string, status: string, message: string, data?: any) => {
+ const event = {
+ timestamp: new Date().toLocaleTimeString('ru-RU'),
+ type,
+ status,
+ message,
+ data
+ };
+ setDebugEvents(prev => [event, ...prev]);
+ };
const updateFormData = (data: Partial) => {
setFormData({ ...formData, ...data });
@@ -100,6 +113,7 @@ export default function ClaimForm() {
formData={formData}
updateFormData={updateFormData}
onNext={nextStep}
+ addDebugEvent={addDebugEvent}
/>
),
},
@@ -124,6 +138,7 @@ export default function ClaimForm() {
onSubmit={handleSubmit}
isPhoneVerified={isPhoneVerified}
setIsPhoneVerified={setIsPhoneVerified}
+ addDebugEvent={addDebugEvent}
/>
),
},
@@ -142,35 +157,45 @@ export default function ClaimForm() {
};
return (
-
-
0 && (
-
- )
- }
- >
-
- {steps.map((item) => (
-
- ))}
-
- {steps[currentStep].content}
-
+
+
+ {/* Левая часть - Форма */}
+
+ 0 && (
+
+ )
+ }
+ >
+
+ {steps.map((item) => (
+
+ ))}
+
+ {steps[currentStep].content}
+
+
+
+ {/* Правая часть - Debug консоль */}
+
+
+
+
);
}