Files
aiform_prod/frontend/src/pages/ClaimForm.tsx
AI Assistant 793177b1e2 feat: Интеграция создания черновика заявки в Step2EventType
-  Вызов n8n webhook после выбора типа события
-  Формирование title из event_type + voucher
-  Передача всех данных: claim_id, contact_id, project_id, event_type
-  Сохранение ticket_id и ticket_number в formData
-  Loading состояние кнопки
-  Debug события для отслеживания
2025-11-02 00:09:35 +03:00

315 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useMemo, useCallback } from 'react';
import { Steps, Card, message, Row, Col } from 'antd';
import Step1Phone from '../components/form/Step1Phone';
import Step1Policy from '../components/form/Step1Policy';
import Step2EventType from '../components/form/Step2EventType';
import StepDocumentUpload from '../components/form/StepDocumentUpload';
import Step3Payment from '../components/form/Step3Payment';
import DebugPanel from '../components/DebugPanel';
import { getDocumentsForEventType } from '../constants/documentConfigs';
import './ClaimForm.css';
const { Step } = Steps;
interface FormData {
// Шаг 1: Phone
phone?: string;
contact_id?: string;
is_new_contact?: boolean;
// Шаг 2: Policy
voucher: string;
claim_id?: string;
session_id?: string;
// Шаг 3: Event Type
eventType?: string;
// Шаги 4+: Documents
documents?: Record<string, {
uploaded: boolean;
data: any;
file_type: string;
skipped?: boolean;
}>;
// Последний шаг: Payment
fullName?: string;
email?: string;
paymentMethod?: string;
bankName?: string;
cardNumber?: string;
accountNumber?: string;
}
export default function ClaimForm() {
// ✅ claim_id будет создан n8n в Step1Phone после SMS верификации
// Не генерируем его локально!
// Генерируем session_id и сохраняем в sessionStorage
const [sessionId] = useState(() => {
let sid = sessionStorage.getItem('session_id');
if (!sid) {
sid = `sess-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
sessionStorage.setItem('session_id', sid);
}
return sid;
});
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState<FormData>({
voucher: '',
claim_id: undefined, // ✅ Будет заполнен n8n в Step1Phone
session_id: sessionId,
paymentMethod: 'sbp',
});
const [isPhoneVerified, setIsPhoneVerified] = useState(false);
const [debugEvents, setDebugEvents] = useState<any[]>([]);
// 🔥 VERSION CHECK: Если видишь это в консоли - фронт обновился!
console.log('🔥 ClaimForm v2.0 - claim_id НЕ генерируется на фронте!');
// Динамически определяем список шагов на основе выбранного eventType
const documentConfigs = formData.eventType ? getDocumentsForEventType(formData.eventType) : [];
const totalDocumentSteps = documentConfigs.length;
const addDebugEvent = (type: string, status: string, message: string, data?: any) => {
const event = {
timestamp: new Date().toLocaleTimeString('ru-RU'),
type,
status,
message,
data: {
...data,
claim_id: formData.claim_id // ✅ Используем claim_id из formData (от n8n)
}
};
setDebugEvents(prev => [event, ...prev]);
};
// ✅ claim_id будет залогирован в Step1Phone после получения от n8n
const updateFormData = useCallback((data: Partial<FormData>) => {
setFormData((prev) => ({ ...prev, ...data }));
}, []);
const nextStep = useCallback(() => {
console.log('⏩ nextStep called');
setCurrentStep((prev) => {
console.log('📍 Current step:', prev, '→ Next:', prev + 1);
return prev + 1;
});
}, []);
const prevStep = useCallback(() => {
console.log('⏪ prevStep called');
setCurrentStep((prev) => {
console.log('📍 Current step:', prev, '→ Prev:', prev - 1);
return prev - 1;
});
}, []);
const handleSubmit = useCallback(async () => {
try {
addDebugEvent('form', 'info', '📤 Отправка заявки на сервер');
const response = await fetch('http://147.45.146.17:8100/api/v1/claims/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
claim_id: formData.claim_id, // ✅ Используем claim_id от n8n
voucher: formData.voucher,
email: formData.email,
phone: formData.phone,
event_type: formData.eventType,
payment_method: formData.paymentMethod,
bank_name: formData.bankName,
card_number: formData.cardNumber,
account_number: formData.accountNumber,
documents: formData.documents || {},
}),
});
const result = await response.json();
if (result.success) {
message.success(`Заявка ${result.claim_number} успешно создана!`);
addDebugEvent('form', 'success', `✅ Заявка ${result.claim_number} создана`);
// Сброс формы (создаём новую заявку, claim_id будет сгенерирован при следующем SMS)
setFormData({
voucher: '',
claim_id: undefined, // ✅ Очищаем для новой заявки
session_id: sessionId,
paymentMethod: 'sbp',
});
setCurrentStep(0);
setIsPhoneVerified(false);
} else {
message.error('Ошибка при создании заявки');
addDebugEvent('form', 'error', '❌ Ошибка создания заявки');
}
} catch (error) {
message.error('Ошибка соединения с сервером');
addDebugEvent('form', 'error', '❌ Ошибка соединения');
console.error(error);
}
}, [formData, sessionId, addDebugEvent]);
// Динамически генерируем шаги на основе выбранного eventType
const steps = useMemo(() => {
const stepsArray: any[] = [];
// Шаг 1: Phone (телефон + SMS верификация)
stepsArray.push({
title: 'Телефон',
description: 'Подтверждение по SMS',
content: (
<Step1Phone
formData={{ ...formData, session_id: sessionId }} // ✅ claim_id будет создан n8n
updateFormData={updateFormData}
onNext={nextStep}
onPrev={prevStep}
isPhoneVerified={isPhoneVerified}
setIsPhoneVerified={setIsPhoneVerified}
addDebugEvent={addDebugEvent}
/>
),
});
// Шаг 2: Policy (всегда)
stepsArray.push({
title: 'Проверка полиса',
description: 'Полис ERV',
content: (
<Step1Policy
formData={{ ...formData, session_id: sessionId }} // ✅ claim_id уже в formData от n8n
updateFormData={updateFormData}
onNext={nextStep}
addDebugEvent={addDebugEvent}
/>
),
});
// Шаг 3: Event Type Selection (всегда)
stepsArray.push({
title: 'Тип события',
description: 'Выбор случая',
content: (
<Step2EventType
formData={formData}
updateFormData={updateFormData}
onNext={nextStep}
onPrev={prevStep}
addDebugEvent={addDebugEvent}
/>
),
});
// Шаги 3+: Document Upload (динамически, если выбран eventType)
if (formData.eventType && documentConfigs.length > 0) {
documentConfigs.forEach((docConfig, index) => {
stepsArray.push({
title: `Документ ${index + 1}`,
description: docConfig.name,
content: (
<StepDocumentUpload
key={`doc-${docConfig.file_type}`}
documentConfig={docConfig}
formData={formData}
updateFormData={updateFormData}
onNext={nextStep}
onPrev={prevStep}
isLastDocument={index === documentConfigs.length - 1}
currentDocNumber={index + 1}
totalDocs={documentConfigs.length}
/>
),
});
});
}
// Последний шаг: Payment (всегда)
stepsArray.push({
title: 'Оплата',
description: 'Контакты и выплата',
content: (
<Step3Payment
formData={formData} // ✅ claim_id уже в formData
updateFormData={updateFormData}
onPrev={prevStep}
onSubmit={handleSubmit}
isPhoneVerified={isPhoneVerified}
setIsPhoneVerified={setIsPhoneVerified}
addDebugEvent={addDebugEvent}
/>
),
});
return stepsArray;
}, [formData, documentConfigs, isPhoneVerified, sessionId, nextStep, prevStep, updateFormData, handleSubmit, setIsPhoneVerified, addDebugEvent]);
const handleReset = () => {
setFormData({
voucher: '',
claim_id: undefined, // ✅ Очищаем для новой заявки
session_id: sessionId,
paymentMethod: 'sbp',
});
setCurrentStep(0);
setIsPhoneVerified(false);
message.info('Форма сброшена');
addDebugEvent('system', 'info', '🔄 Форма сброшена');
};
return (
<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, index) => (
<Step
key={`step-${index}`}
title={item.title}
description={item.description}
/>
))}
</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>
);
}