Ticket form: new stack + description step
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "erv-insurance-platform-frontend",
|
||||
"name": "ticket-form-intake-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "ERV Insurance Claims Platform - Frontend",
|
||||
"description": "Ticket Form Intake Platform - Frontend",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
|
||||
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined } from '@ant-design/icons';
|
||||
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined, CopyOutlined } from '@ant-design/icons';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -28,6 +30,7 @@ export default function Step3Payment({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [verifyLoading, setVerifyLoading] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [debugCode, setDebugCode] = useState<string | null>(formData.smsDebugCode ?? null);
|
||||
|
||||
const sendCode = async () => {
|
||||
try {
|
||||
@@ -41,7 +44,7 @@ export default function Step3Payment({
|
||||
|
||||
addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
|
||||
|
||||
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/send', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/sms/send`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone }),
|
||||
@@ -58,6 +61,8 @@ export default function Step3Payment({
|
||||
message.success('Код отправлен на ваш телефон');
|
||||
setCodeSent(true);
|
||||
if (result.debug_code) {
|
||||
setDebugCode(result.debug_code);
|
||||
updateFormData({ smsDebugCode: result.debug_code });
|
||||
message.info(`DEBUG: Код ${result.debug_code}`);
|
||||
}
|
||||
} else {
|
||||
@@ -85,7 +90,7 @@ export default function Step3Payment({
|
||||
|
||||
addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
|
||||
|
||||
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/verify', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/sms/verify`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone, code }),
|
||||
@@ -99,6 +104,8 @@ export default function Step3Payment({
|
||||
verified: true
|
||||
});
|
||||
message.success('Телефон подтвержден!');
|
||||
setDebugCode(null);
|
||||
updateFormData({ smsDebugCode: undefined });
|
||||
setIsPhoneVerified(true);
|
||||
} else {
|
||||
addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, {
|
||||
@@ -229,6 +236,35 @@ export default function Step3Payment({
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{debugCode && !isPhoneVerified && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 8,
|
||||
padding: 12,
|
||||
background: '#fffbe6',
|
||||
borderRadius: 8,
|
||||
border: '1px dashed #faad14',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<strong>DEBUG код:</strong> {debugCode}
|
||||
</span>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(debugCode);
|
||||
message.success('Код скопирован');
|
||||
}}
|
||||
>
|
||||
Скопировать
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
121
frontend/src/components/form/StepDescription.tsx
Normal file
121
frontend/src/components/form/StepDescription.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Form, Input, Button, Typography, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
interface Props {
|
||||
formData: any;
|
||||
updateFormData: (data: any) => void;
|
||||
onPrev: () => void;
|
||||
onNext: () => void;
|
||||
}
|
||||
|
||||
export default function StepDescription({
|
||||
formData,
|
||||
updateFormData,
|
||||
onPrev,
|
||||
onNext,
|
||||
}: Props) {
|
||||
const [form] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
problemDescription: formData.problemDescription ?? '',
|
||||
});
|
||||
}, [form, formData.problemDescription]);
|
||||
|
||||
const handleContinue = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
if (!formData.session_id) {
|
||||
message.error('Не найден session_id. Попробуйте обновить страницу.');
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
|
||||
const response = await fetch('/api/v1/claims/description', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
session_id: formData.session_id,
|
||||
claim_id: formData.claim_id,
|
||||
phone: formData.phone,
|
||||
email: formData.email,
|
||||
problem_description: values.problemDescription,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Ошибка API: ${response.status}`);
|
||||
}
|
||||
|
||||
message.success('Описание отправлено, продолжаем заполнение');
|
||||
updateFormData(values);
|
||||
onNext();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('Не получилось сохранить описание. Попробуйте ещё раз.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<Button onClick={onPrev} size="large">
|
||||
← Назад
|
||||
</Button>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: 24,
|
||||
padding: 24,
|
||||
background: '#f6f8fa',
|
||||
borderRadius: 12,
|
||||
border: '1px solid #e0e6ed',
|
||||
}}
|
||||
>
|
||||
<Paragraph style={{ fontSize: 18, fontWeight: 600, marginBottom: 8 }}>
|
||||
📄 Опишите проблему
|
||||
</Paragraph>
|
||||
<Paragraph type="secondary" style={{ marginBottom: 24 }}>
|
||||
Расскажите, что произошло, в свободной форме. Чем больше деталей —
|
||||
тем быстрее команда сможет разобраться, какие документы нужны и куда
|
||||
направить заявку.
|
||||
</Paragraph>
|
||||
|
||||
<Form layout="vertical" form={form}>
|
||||
<Form.Item
|
||||
label="Описание ситуации"
|
||||
name="problemDescription"
|
||||
rules={[
|
||||
{ required: true, message: 'Поле обязательно' },
|
||||
{
|
||||
min: 20,
|
||||
message: 'Опишите, пожалуйста, минимум в пару предложений',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TextArea
|
||||
autoSize={{ minRows: 6 }}
|
||||
maxLength={3000}
|
||||
showCount
|
||||
placeholder="Например: заключил договор на оказание услуг..., деньги списали..., услугу не выполнили..."
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<Button type="primary" size="large" onClick={handleContinue} loading={submitting}>
|
||||
Продолжить →
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ interface Props {
|
||||
totalDocs: number;
|
||||
}
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://147.45.146.17:8100';
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||
|
||||
const StepDocumentUpload: React.FC<Props> = ({
|
||||
documentConfig,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -9,6 +10,8 @@ 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 {
|
||||
@@ -23,6 +26,7 @@ interface FormData {
|
||||
session_id?: string;
|
||||
project_id?: string; // ✅ ID проекта в vTiger (полис)
|
||||
is_new_project?: boolean; // ✅ Флаг: создан новый проект
|
||||
problemDescription?: string;
|
||||
|
||||
// Шаг 3: Event Type
|
||||
eventType?: string;
|
||||
@@ -117,7 +121,7 @@ export default function ClaimForm() {
|
||||
try {
|
||||
addDebugEvent('form', 'info', '📤 Отправка заявки на сервер');
|
||||
|
||||
const response = await fetch('http://147.45.146.17:8100/api/v1/claims/create', {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/claims/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -183,7 +187,21 @@ export default function ClaimForm() {
|
||||
),
|
||||
});
|
||||
|
||||
// Шаг 2: Policy (всегда)
|
||||
// Шаг 2: свободное описание
|
||||
stepsArray.push({
|
||||
title: 'Описание',
|
||||
description: 'Что случилось?',
|
||||
content: (
|
||||
<StepDescription
|
||||
formData={formData}
|
||||
updateFormData={updateFormData}
|
||||
onPrev={prevStep}
|
||||
onNext={nextStep}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
// Шаг 3: Policy (всегда)
|
||||
stepsArray.push({
|
||||
title: 'Проверка полиса',
|
||||
description: 'Полис ERV',
|
||||
@@ -197,7 +215,7 @@ export default function ClaimForm() {
|
||||
),
|
||||
});
|
||||
|
||||
// Шаг 3: Event Type Selection (всегда)
|
||||
// Шаг 4: Event Type Selection (всегда)
|
||||
stepsArray.push({
|
||||
title: 'Тип события',
|
||||
description: 'Выбор случая',
|
||||
|
||||
@@ -8,11 +8,11 @@ export default defineConfig({
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://host.docker.internal:8100',
|
||||
target: 'http://host.docker.internal:8200',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/events': {
|
||||
target: 'http://host.docker.internal:8100',
|
||||
target: 'http://host.docker.internal:8200',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user