diff --git a/backend/app/services/ocr_service.py b/backend/app/services/ocr_service.py
index 75d103a..5271ed7 100644
--- a/backend/app/services/ocr_service.py
+++ b/backend/app/services/ocr_service.py
@@ -174,3 +174,4 @@ class OCRService:
# Глобальный экземпляр
ocr_service = OCRService()
+
diff --git a/backend/app/services/s3_service.py b/backend/app/services/s3_service.py
index 1cb87a5..b0d7760 100644
--- a/backend/app/services/s3_service.py
+++ b/backend/app/services/s3_service.py
@@ -102,3 +102,4 @@ class S3Service:
# Глобальный экземпляр
s3_service = S3Service()
+
diff --git a/backend/db/migrations/002_create_claims_draft.sql b/backend/db/migrations/002_create_claims_draft.sql
index b629d0b..db8b15f 100644
--- a/backend/db/migrations/002_create_claims_draft.sql
+++ b/backend/db/migrations/002_create_claims_draft.sql
@@ -24,3 +24,4 @@ COMMENT ON COLUMN claims_draft.session_id IS 'Уникальный ID сесси
COMMENT ON COLUMN claims_draft.current_step IS 'Номер шага где пользователь остановился';
COMMENT ON COLUMN claims_draft.form_data IS 'Все данные формы в JSON формате';
+
diff --git a/frontend/src/components/DebugPanel.tsx b/frontend/src/components/DebugPanel.tsx
index 247918d..9ab9a25 100644
--- a/frontend/src/components/DebugPanel.tsx
+++ b/frontend/src/components/DebugPanel.tsx
@@ -186,34 +186,67 @@ export default function DebugPanel({ events, formData }: Props) {
)}
- {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)}...
-
-
- )}
-
+ {event.type === 'upload' && event.data.files && (
+
+ {event.data.files.map((file: any, idx: number) => (
+
+ File}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#ce9178', fontSize: 10 }}
+ >
+ {file.filename}
+
+ File ID}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#569cd6', fontSize: 9, fontFamily: 'monospace' }}
+ >
+ {file.file_id}
+
+ Size}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', color: '#dcdcaa' }}
+ >
+ {(file.size / 1024).toFixed(1)} KB
+
+ {file.url && (
+ S3 URL}
+ labelStyle={{ background: '#252526' }}
+ contentStyle={{ background: '#1e1e1e', fontSize: 9, wordBreak: 'break-all' }}
+ >
+
+ {file.url}
+
+
+
+ )}
+
+ ))}
+
)}
)}
@@ -234,3 +267,4 @@ export default function DebugPanel({ events, formData }: Props) {
);
}
+
diff --git a/frontend/src/components/form/Step2Details.tsx b/frontend/src/components/form/Step2Details.tsx
index a71794d..2edde8d 100644
--- a/frontend/src/components/form/Step2Details.tsx
+++ b/frontend/src/components/form/Step2Details.tsx
@@ -1,9 +1,9 @@
-import { Form, Input, DatePicker, Select, Button, Upload, message } from 'antd';
+import { Form, Input, Button, Select, DatePicker, Upload, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
-import type { UploadFile } from 'antd/es/upload/interface';
import { useState } from 'react';
+import type { UploadFile } from 'antd/es/upload/interface';
+import dayjs from 'dayjs';
-const { TextArea } = Input;
const { Option } = Select;
interface Props {
@@ -11,53 +11,80 @@ interface Props {
updateFormData: (data: any) => void;
onNext: () => void;
onPrev: () => void;
+ addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
-export default function Step2Details({ formData, updateFormData, onNext, onPrev }: Props) {
+// Типы страховых случаев из erv_ticket
+const EVENT_TYPES = [
+ { value: 'delay_flight', label: 'Задержка авиарейса (более 3 часов)' },
+ { value: 'cancel_flight', label: 'Отмена авиарейса' },
+ { value: 'miss_connection', label: 'Пропуск (задержка прибытия) стыковочного рейса (авиа/жд/паром и тд)' },
+ { value: 'emergency_landing', label: 'Посадка воздушного судна на запасной аэродром' },
+ { value: 'delay_train', label: 'Задержка отправки поезда' },
+ { value: 'cancel_train', label: 'Отмена поезда' },
+ { value: 'delay_ferry', label: 'Задержка/отмена отправки парома/круизного судна' },
+];
+
+export default function Step2Details({ formData, updateFormData, onNext, onPrev, addDebugEvent }: Props) {
const [form] = Form.useForm();
const [fileList, setFileList] = useState([]);
+ const [uploading, setUploading] = useState(false);
const handleNext = async () => {
try {
const values = await form.validateFields();
- updateFormData({
- ...values,
- incidentDate: values.incidentDate?.format('YYYY-MM-DD'),
- uploadedFiles: fileList.map(f => f.uid),
- });
+
+ // Если есть файлы - загружаем
+ if (fileList.length > 0) {
+ setUploading(true);
+
+ addDebugEvent?.('upload', 'pending', `📤 Загружаю ${fileList.length} документ(ов) в S3...`, {
+ count: fileList.length
+ });
+
+ const formData = new FormData();
+ fileList.forEach((file: any) => {
+ if (file.originFileObj) {
+ formData.append('files', file.originFileObj);
+ }
+ });
+
+ const uploadResponse = await fetch('http://147.45.146.17:8100/api/v1/upload/files?folder=documents', {
+ method: 'POST',
+ body: formData,
+ });
+
+ const uploadResult = await uploadResponse.json();
+
+ if (uploadResult.success) {
+ addDebugEvent?.('upload', 'success', `✅ Документы загружены: ${uploadResult.uploaded_count}/${uploadResult.total_count}`, {
+ files: uploadResult.files
+ });
+
+ updateFormData({
+ ...values,
+ uploadedFiles: uploadResult.files
+ });
+ } else {
+ message.error('Ошибка загрузки документов');
+ setUploading(false);
+ return;
+ }
+
+ setUploading(false);
+ } else {
+ updateFormData(values);
+ }
+
onNext();
} catch (error) {
message.error('Заполните все обязательные поля');
+ setUploading(false);
}
};
- const uploadProps = {
- fileList,
- beforeUpload: (file: File) => {
- const isImage = file.type.startsWith('image/');
- const isPDF = file.type === 'application/pdf';
- if (!isImage && !isPDF) {
- message.error('Можно загружать только изображения и PDF');
- return false;
- }
- const isLt10M = file.size / 1024 / 1024 < 10;
- if (!isLt10M) {
- message.error('Файл должен быть меньше 10MB');
- return false;
- }
-
- setFileList([...fileList, {
- uid: Math.random().toString(),
- name: file.name,
- status: 'done',
- url: URL.createObjectURL(file),
- } as UploadFile]);
-
- return false; // Отключаем автозагрузку
- },
- onRemove: (file: UploadFile) => {
- setFileList(fileList.filter(f => f.uid !== file.uid));
- },
+ const handleUploadChange = ({ fileList: newFileList }: any) => {
+ setFileList(newFileList);
};
return (
@@ -67,56 +94,115 @@ export default function Step2Details({ formData, updateFormData, onNext, onPrev
initialValues={formData}
style={{ marginTop: 24 }}
>
-
-
-
+ {/* Индикатор что полис найден */}
+ {formData.voucher && (
+
+ ✅ Полис найден
+
+ )}
-
-
-
-
- }>Загрузить файлы
+
+
+
+
+
+ {
+ const isLt15M = file.size / 1024 / 1024 < 15;
+ if (!isLt15M) {
+ message.error(`${file.name}: файл больше 15MB`);
+ return Upload.LIST_IGNORE;
+ }
+ if (fileList.length >= 10) {
+ message.error('Максимум 10 файлов');
+ return Upload.LIST_IGNORE;
+ }
+ return false;
+ }}
+ accept="image/*,.pdf,.heic,.heif"
+ multiple
+ maxCount={10}
+ showUploadList={{
+ showPreviewIcon: true,
+ showRemoveIcon: true,
+ }}
+ >
+ } size="large" block disabled={fileList.length >= 10}>
+ Загрузить файлы (до 10 шт, макс 15MB каждый)
+
-
- Максимум 10 MB на файл. Форматы: JPG, PNG, PDF, HEIC
+
+ Загружено: {fileList.length}/10 файлов
-
-
-