From d2777aeabf0785dfb1336b3b2d227552d3be8414 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sat, 25 Oct 2025 09:27:56 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20Step2=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BD=20+=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=20Debug=20Panel=20=D1=81=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D1=8B=D0=BC=D0=B8=20S3=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step2Details (по скриншоту): ✅ Индикатор '✅ Полис найден' вверху ✅ Select с типами событий из erv_ticket: - Задержка авиарейса (более 3 часов) - Отмена авиарейса - Пропуск стыковочного рейса - Посадка на запасной аэродром - Задержка отправки поезда - Отмена поезда - Задержка/отмена парома/круизного судна ✅ Дата наступления страхового случая (DatePicker) ✅ Номер рейса/поезда/парома ✅ Загрузка подтверждающих документов: - Посадочный талон, билет, справка и т.д. - До 10 файлов по 15MB - HEIC, PDF, фото Debug Panel улучшения: ✅ Полные S3 URL (не обрезанные) ✅ Кнопка '🔗 Открыть в новой вкладке' ✅ word-break: break-all для длинных URL ✅ Показывает все файлы из массива ✅ Для каждого файла: - Filename - File ID (UUID) - Size (KB) - Полный S3 URL (кликабельный) Теперь в Debug видно КУДА загрузилось: https://s3.twcstorage.ru/f9825c87-.../policies/20251024_213045_abc123_file.jpg Можно кликнуть и посмотреть глазами! 👀 --- backend/app/services/ocr_service.py | 1 + backend/app/services/s3_service.py | 1 + .../db/migrations/002_create_claims_draft.sql | 1 + frontend/src/components/DebugPanel.tsx | 90 ++++--- frontend/src/components/form/Step2Details.tsx | 220 ++++++++++++------ frontend/src/pages/ClaimForm.tsx | 1 + 6 files changed, 219 insertions(+), 95 deletions(-) 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 => ( + + ))} -