feat: Split-screen с Debug панелью в реальном времени!
Новый UI: ✅ Split-screen layout: - Слева (60%): форма заявки - Справа (40%): Debug Console в реальном времени Компонент DebugPanel.tsx: ✅ Темная тема (VS Code style) ✅ Timeline с событиями ✅ Real-time обновления ✅ Показывает: - Form Data (JSON в реальном времени) - Events Log с иконками и цветами - Детали каждого события События которые отображаются: 1. policy_check: - ✅ Полис найден в MySQL БД - ⚠️ Полис не найден - Показывает: voucher, found status 2. upload: - 📤 Загружаю X файлов в S3 - ✅ Загружено в S3: X/Y - Показывает: file_id, size, S3 URL 3. ocr: - 🔍 Запущен OCR - 📄 OCR завершен: XXX символов - Показывает: текст preview 4. ai_analysis: - 🤖 AI: policy/garbage, confidence: 95% - 🗑️ ШЛЯПА DETECTED! (пользователю не говорим) - Показывает: document_type, is_valid, confidence, extracted_data 5. sms: - 📱 Отправляю SMS - ✅ SMS отправлен (DEBUG mode) - 🔐 Проверяю код - ✅ Телефон подтвержден - Показывает: phone, debug_code UX: - Sticky panel (прилипает при скролле) - Monospace шрифт для данных - Цветовая кодировка статусов - JSON форматирование Layout: - Row + Col от Ant Design - Responsive: mobile = 1 column, desktop = split Теперь видно ВСЁ что происходит в реальном времени! 🔍
This commit is contained in:
236
frontend/src/components/DebugPanel.tsx
Normal file
236
frontend/src/components/DebugPanel.tsx
Normal file
@@ -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 <CheckCircleOutlined style={{ color: '#52c41a' }} />;
|
||||
case 'pending': return <LoadingOutlined style={{ color: '#1890ff' }} />;
|
||||
case 'warning': return <ExclamationCircleOutlined style={{ color: '#faad14' }} />;
|
||||
case 'error': return <CloseCircleOutlined style={{ color: '#f5222d' }} />;
|
||||
default: return <CheckCircleOutlined />;
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<div style={{
|
||||
position: 'sticky',
|
||||
top: 20,
|
||||
height: 'calc(100vh - 40px)',
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
<Card
|
||||
title="🔧 Debug Console"
|
||||
size="small"
|
||||
style={{
|
||||
background: '#1e1e1e',
|
||||
color: '#d4d4d4',
|
||||
border: '1px solid #333'
|
||||
}}
|
||||
headStyle={{
|
||||
background: '#252526',
|
||||
color: '#fff',
|
||||
borderBottom: '1px solid #333'
|
||||
}}
|
||||
bodyStyle={{ padding: 12 }}
|
||||
>
|
||||
{/* Текущие данные формы */}
|
||||
<div style={{ marginBottom: 16, padding: 12, background: '#2d2d30', borderRadius: 4 }}>
|
||||
<div style={{ fontSize: 12, color: '#9cdcfe', marginBottom: 8, fontFamily: 'monospace' }}>
|
||||
<strong>Form Data:</strong>
|
||||
</div>
|
||||
<pre style={{
|
||||
margin: 0,
|
||||
fontSize: 11,
|
||||
color: '#ce9178',
|
||||
fontFamily: 'Consolas, monospace',
|
||||
maxHeight: 150,
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
{JSON.stringify(formData, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* События */}
|
||||
<div style={{ marginBottom: 8, fontSize: 12, color: '#9cdcfe', fontFamily: 'monospace' }}>
|
||||
<strong>Events Log:</strong>
|
||||
</div>
|
||||
|
||||
<Timeline style={{ marginTop: 16 }}>
|
||||
{events.length === 0 && (
|
||||
<Timeline.Item color="gray">
|
||||
<span style={{ color: '#888', fontSize: 12 }}>Нет событий...</span>
|
||||
</Timeline.Item>
|
||||
)}
|
||||
|
||||
{events.map((event, index) => (
|
||||
<Timeline.Item
|
||||
key={index}
|
||||
dot={getIcon(event.status)}
|
||||
>
|
||||
<div style={{ fontSize: 11, fontFamily: 'monospace' }}>
|
||||
<div style={{ color: '#888', marginBottom: 4 }}>
|
||||
{event.timestamp}
|
||||
</div>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Tag color={getColor(event.status)} style={{ fontSize: 11 }}>
|
||||
{event.type.toUpperCase()}
|
||||
</Tag>
|
||||
<span style={{ color: '#d4d4d4' }}>{event.message}</span>
|
||||
</div>
|
||||
|
||||
{event.data && (
|
||||
<div style={{
|
||||
marginTop: 8,
|
||||
padding: 8,
|
||||
background: '#1e1e1e',
|
||||
borderRadius: 4,
|
||||
border: '1px solid #333'
|
||||
}}>
|
||||
{event.type === 'policy_check' && event.data.found !== undefined && (
|
||||
<Descriptions size="small" column={1} bordered>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Found</span>}
|
||||
labelStyle={{ background: '#252526', color: '#9cdcfe' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: event.data.found ? '#4ec9b0' : '#f48771' }}
|
||||
>
|
||||
{event.data.found ? 'TRUE' : 'FALSE'}
|
||||
</Descriptions.Item>
|
||||
{event.data.holder_name && (
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Holder</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: '#ce9178' }}
|
||||
>
|
||||
{event.data.holder_name}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
)}
|
||||
|
||||
{event.type === 'ocr' && (
|
||||
<pre style={{
|
||||
margin: 0,
|
||||
fontSize: 10,
|
||||
color: '#ce9178',
|
||||
maxHeight: 100,
|
||||
overflow: 'auto',
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}>
|
||||
{event.data.text?.substring(0, 300)}...
|
||||
</pre>
|
||||
)}
|
||||
|
||||
{event.type === 'ai_analysis' && (
|
||||
<Descriptions size="small" column={1} bordered>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Type</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: '#4ec9b0' }}
|
||||
>
|
||||
{event.data.document_type}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Valid</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: event.data.is_valid ? '#4ec9b0' : '#f48771' }}
|
||||
>
|
||||
{event.data.is_valid ? 'TRUE' : 'FALSE'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Confidence</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
|
||||
>
|
||||
{(event.data.confidence * 100).toFixed(0)}%
|
||||
</Descriptions.Item>
|
||||
{event.data.extracted_data && Object.keys(event.data.extracted_data).length > 0 && (
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Extracted</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e' }}
|
||||
>
|
||||
<pre style={{
|
||||
margin: 0,
|
||||
fontSize: 10,
|
||||
color: '#ce9178',
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}>
|
||||
{JSON.stringify(event.data.extracted_data, null, 2)}
|
||||
</pre>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
)}
|
||||
|
||||
{event.type === 'upload' && (
|
||||
<Descriptions size="small" column={1} bordered>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>File ID</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: '#569cd6', fontSize: 10 }}
|
||||
>
|
||||
{event.data.file_id}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>Size</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
|
||||
>
|
||||
{(event.data.size / 1024).toFixed(1)} KB
|
||||
</Descriptions.Item>
|
||||
{event.data.url && (
|
||||
<Descriptions.Item
|
||||
label={<span style={{ color: '#9cdcfe' }}>S3 URL</span>}
|
||||
labelStyle={{ background: '#252526' }}
|
||||
contentStyle={{ background: '#1e1e1e', fontSize: 9 }}
|
||||
>
|
||||
<a href={event.data.url} target="_blank" rel="noopener noreferrer" style={{ color: '#4ec9b0' }}>
|
||||
{event.data.url.substring(0, 50)}...
|
||||
</a>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
|
||||
{events.length > 0 && (
|
||||
<div style={{ marginTop: 16, padding: 8, background: '#2d2d30', borderRadius: 4, textAlign: 'center' }}>
|
||||
<span style={{ fontSize: 11, color: '#888' }}>
|
||||
Total events: {events.length}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
|
||||
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<FormData>) => {
|
||||
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 (
|
||||
<div className="claim-form-container">
|
||||
<Card
|
||||
title="Подать заявку на выплату"
|
||||
className="claim-form-card"
|
||||
extra={
|
||||
currentStep > 0 && (
|
||||
<button
|
||||
onClick={handleReset}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
background: '#fff',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
🔄 Начать заново
|
||||
</button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Steps current={currentStep} className="steps">
|
||||
{steps.map((item) => (
|
||||
<Step key={item.title} title={item.title} />
|
||||
))}
|
||||
</Steps>
|
||||
<div className="steps-content">{steps[currentStep].content}</div>
|
||||
</Card>
|
||||
<div className="claim-form-container" style={{ padding: '20px', background: '#f0f2f5' }}>
|
||||
<Row gutter={16}>
|
||||
{/* Левая часть - Форма */}
|
||||
<Col xs={24} lg={14}>
|
||||
<Card
|
||||
title="Подать заявку на выплату"
|
||||
className="claim-form-card"
|
||||
extra={
|
||||
currentStep > 0 && (
|
||||
<button
|
||||
onClick={handleReset}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
background: '#fff',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
🔄 Начать заново
|
||||
</button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Steps current={currentStep} className="steps">
|
||||
{steps.map((item) => (
|
||||
<Step key={item.title} title={item.title} />
|
||||
))}
|
||||
</Steps>
|
||||
<div className="steps-content">{steps[currentStep].content}</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* Правая часть - Debug консоль */}
|
||||
<Col xs={24} lg={10}>
|
||||
<DebugPanel events={debugEvents} formData={formData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user