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 && ( +
+ ✅ Полис найден +
+ )} - + {EVENT_TYPES.map(type => ( + + ))} -