Files
aiform_prod/frontend/src/pages/ClaimForm.tsx
2025-11-14 19:06:36 +03:00

337 lines
11 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 StepDescription from '../components/form/StepDescription';
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 API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
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;
project_id?: string; // ✅ ID проекта в vTiger (полис)
is_new_project?: boolean; // ✅ Флаг: создан новый проект
problemDescription?: string;
// Шаг 3: Event Type
eventType?: string;
ticket_id?: string; // ✅ ID заявки в vTiger (HelpDesk)
ticket_number?: string; // ✅ Номер заявки (HD001234)
// Шаги 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(`${API_BASE_URL}/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: свободное описание
stepsArray.push({
title: 'Описание',
description: 'Что случилось?',
content: (
<StepDescription
formData={formData}
updateFormData={updateFormData}
onPrev={prevStep}
onNext={nextStep}
/>
),
});
// Шаг 3: Policy (всегда)
stepsArray.push({
title: 'Проверка полиса',
description: 'Полис ERV',
content: (
<Step1Policy
formData={{ ...formData, session_id: sessionId }} // ✅ claim_id уже в formData от n8n
updateFormData={updateFormData}
onNext={nextStep}
addDebugEvent={addDebugEvent}
/>
),
});
// Шаг 4: 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>
);
}