From 122af07779da618b5441746d8f8e095e039ca281 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Tue, 28 Oct 2025 12:03:12 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=A3=D0=BC=D0=BD=D0=B0=D1=8F=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D0=B0=20Step2=20=D1=81=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=BC=20=D1=80=D0=B0=D1=81=D0=BF=D0=BE=D0=B7=D0=BD=D0=B0=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit πŸ€– ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ Π½Π° OCR/AI для извлСчСния Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²: βœ… ИзмСнСния: - Π£Π±Ρ€Π°Π½ Ρ€ΡƒΡ‡Π½ΠΎΠΉ Π²Π²ΠΎΠ΄ ΠΏΠΎΠ»Π΅ΠΉ (Π΄Π°Ρ‚Π°, Π½ΠΎΠΌΠ΅Ρ€ рСйса ΠΈ Ρ‚Π΄) - Π”ΠΎΠ±Π°Π²Π»Π΅Π½Π° умная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² Π² зависимости ΠΎΡ‚ Ρ‚ΠΈΠΏΠ° события - ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ‚ΠΈΠΏ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π° ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ file_type для n8n - Валидация ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ΠΏΠ΅Ρ€Π΅Π΄ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΎΠΌ πŸ“‹ Π’ΠΈΠΏΡ‹ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ΠΈ ΠΈΡ… file_type: 1. Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° рСйса: - flight_delay_boarding_or_ticket (обяз) - flight_delay_confirmation (обяз) 2. ΠžΡ‚ΠΌΠ΅Π½Π° рСйса: - flight_cancel_ticket (обяз) - flight_cancel_notice (обяз) 3. ΠŸΡ€ΠΎΠΏΡƒΡΠΊ стыковки: - connection_arrival_boarding (обяз) - connection_departure_boarding_or_ticket (обяз) - connection_delay_proof (ΠΎΠΏΡ†) 4. ПоСзд (Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ°): - train_ticket (обяз) - train_delay_proof (обяз) 5. ПоСзд (ΠΎΡ‚ΠΌΠ΅Π½Π°): - train_ticket (обяз) - train_cancel_proof (обяз) 6. ΠŸΠ°Ρ€ΠΎΠΌ: - ferry_ticket (обяз) - ferry_delay_proof (обяз) 7. Запасной аэродром: - emergency_boarding_or_ticket (обяз) - emergency_landing_proof (обяз) πŸ”‘ file_type позволяСт n8n Ρ€Π°Π·Π΄Π΅Π»ΡΡ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠΊΠΈ ΠΈ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ€Π°Π·Π½Ρ‹Π΅ AI ΠΏΡ€ΠΎΠΌΠΏΡ‚Ρ‹ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°. Backup старой вСрсии: Step2Details.OLD_MANUAL_INPUT.tsx --- frontend/src/components/form/Step2Details.tsx | 531 ++++++++++-------- 1 file changed, 296 insertions(+), 235 deletions(-) diff --git a/frontend/src/components/form/Step2Details.tsx b/frontend/src/components/form/Step2Details.tsx index 76632fd..26f8c10 100644 --- a/frontend/src/components/form/Step2Details.tsx +++ b/frontend/src/components/form/Step2Details.tsx @@ -1,6 +1,6 @@ -import { Form, Input, Button, Select, DatePicker, Upload, message, Spin, Alert } from 'antd'; -import { UploadOutlined, LoadingOutlined } from '@ant-design/icons'; -import { useState } from 'react'; +import { Form, Button, Select, Upload, message, Spin, Alert, Card } from 'antd'; +import { UploadOutlined, LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; +import { useState, useEffect, useRef } from 'react'; import type { UploadFile } from 'antd/es/upload/interface'; import dayjs from 'dayjs'; @@ -14,105 +14,276 @@ interface Props { addDebugEvent?: (type: string, status: string, message: string, data?: any) => void; } -// Π’ΠΈΠΏΡ‹ страховых случаСв ΠΈΠ· erv_ticket +// Π’ΠΈΠΏΡ‹ страховых случаСв const EVENT_TYPES = [ { value: 'delay_flight', label: 'Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° авиарСйса (Π±ΠΎΠ»Π΅Π΅ 3 часов)' }, { value: 'cancel_flight', label: 'ΠžΡ‚ΠΌΠ΅Π½Π° авиарСйса' }, - { value: 'miss_connection', label: 'ΠŸΡ€ΠΎΠΏΡƒΡΠΊ (Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ° прибытия) стыковочного рСйса (Π°Π²ΠΈΠ°/ΠΆΠ΄/ΠΏΠ°Ρ€ΠΎΠΌ ΠΈ Ρ‚Π΄)' }, + { value: 'miss_connection', label: 'ΠŸΡ€ΠΎΠΏΡƒΡΠΊ (Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠ° прибытия) стыковочного рСйса' }, { value: 'emergency_landing', label: 'Посадка Π²ΠΎΠ·Π΄ΡƒΡˆΠ½ΠΎΠ³ΠΎ судна Π½Π° запасной аэродром' }, { value: 'delay_train', label: 'Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ΠΏΠΎΠ΅Π·Π΄Π°' }, { value: 'cancel_train', label: 'ΠžΡ‚ΠΌΠ΅Π½Π° ΠΏΠΎΠ΅Π·Π΄Π°' }, { value: 'delay_ferry', label: 'Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ°/ΠΎΡ‚ΠΌΠ΅Π½Π° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ΠΏΠ°Ρ€ΠΎΠΌΠ°/ΠΊΡ€ΡƒΠΈΠ·Π½ΠΎΠ³ΠΎ судна' }, ]; +// ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° события с ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌΠΈ file_type +const DOCUMENT_CONFIGS: Record = { + delay_flight: [ + { + name: "ΠŸΠΎΡΠ°Π΄ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚Π°Π»ΠΎΠ½ ΠΈΠ»ΠΈ Π‘ΠΈΠ»Π΅Ρ‚", + field: "boarding_or_ticket", + file_type: "flight_delay_boarding_or_ticket", + required: true, + maxFiles: 1, + description: "Boarding pass ΠΈΠ»ΠΈ ticket/booking confirmation" + }, + { + name: "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ", + field: "delay_confirmation", + file_type: "flight_delay_confirmation", + required: true, + maxFiles: 3, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΎΡ‚ АК, email/SMS, ΠΈΠ»ΠΈ Ρ„ΠΎΡ‚ΠΎ Ρ‚Π°Π±Π»ΠΎ" + } + ], + + cancel_flight: [ + { + name: "Π‘ΠΈΠ»Π΅Ρ‚", + field: "ticket", + file_type: "flight_cancel_ticket", + required: true, + maxFiles: 1, + description: "Ticket/booking confirmation" + }, + { + name: "Π£Π²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅ ΠΎΠ± ΠΎΡ‚ΠΌΠ΅Π½Π΅", + field: "cancellation_notice", + file_type: "flight_cancel_notice", + required: true, + maxFiles: 3, + description: "Email, SMS ΠΈΠ»ΠΈ ΡΠΊΡ€ΠΈΠ½ΡˆΠΎΡ‚ ΠΈΠ· прилоТСния АК" + } + ], + + miss_connection: [ + { + name: "ΠŸΠΎΡΠ°Π΄ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚Π°Π»ΠΎΠ½ рСйса ΠŸΠ Π˜Π‘Π«Π’Π˜Π―", + field: "arrival_boarding", + file_type: "connection_arrival_boarding", + required: true, + maxFiles: 1, + description: "Boarding pass рСйса, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ задСрТался" + }, + { + name: "ΠŸΠΎΡΠ°Π΄ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚Π°Π»ΠΎΠ½ Π˜Π›Π˜ Π‘ΠΈΠ»Π΅Ρ‚ рСйса ΠžΠ’ΠŸΠ ΠΠ’Π›Π•ΠΠ˜Π―", + field: "departure_boarding_or_ticket", + file_type: "connection_departure_boarding_or_ticket", + required: true, + maxFiles: 1, + description: "Boarding pass (Ссли успСли) Π˜Π›Π˜ Π±ΠΈΠ»Π΅Ρ‚ (Ссли Π½Π΅ успСли)" + }, + { + name: "Π”ΠΎΠΊΠ°Π·Π°Ρ‚Π΅Π»ΡŒΡΡ‚Π²ΠΎ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ)", + field: "delay_proof", + file_type: "connection_delay_proof", + required: false, + maxFiles: 5, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ°, Ρ„ΠΎΡ‚ΠΎ Ρ‚Π°Π±Π»ΠΎ, email/SMS" + } + ], + + delay_train: [ + { + name: "Π‘ΠΈΠ»Π΅Ρ‚ Π½Π° ΠΏΠΎΠ΅Π·Π΄", + field: "train_ticket", + file_type: "train_ticket", + required: true, + maxFiles: 1, + description: "Π‘ΠΈΠ»Π΅Ρ‚ Π Π–Π” ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ·Ρ‡ΠΈΠΊΠ°" + }, + { + name: "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ", + field: "delay_proof", + file_type: "train_delay_proof", + required: true, + maxFiles: 3, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΎΡ‚ Π Π–Π”, Ρ„ΠΎΡ‚ΠΎ Ρ‚Π°Π±Π»ΠΎ, ΡΠΊΡ€ΠΈΠ½ΡˆΠΎΡ‚ прилоТСния" + } + ], + + cancel_train: [ + { + name: "Π‘ΠΈΠ»Π΅Ρ‚ Π½Π° ΠΏΠΎΠ΅Π·Π΄", + field: "train_ticket", + file_type: "train_ticket", + required: true, + maxFiles: 1, + description: "Π‘ΠΈΠ»Π΅Ρ‚ Π Π–Π” ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ·Ρ‡ΠΈΠΊΠ°" + }, + { + name: "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΎΡ‚ΠΌΠ΅Π½Ρ‹", + field: "cancel_proof", + file_type: "train_cancel_proof", + required: true, + maxFiles: 3, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΎΡ‚ Π Π–Π”, Ρ„ΠΎΡ‚ΠΎ Ρ‚Π°Π±Π»ΠΎ, ΡΠΊΡ€ΠΈΠ½ΡˆΠΎΡ‚ прилоТСния" + } + ], + + delay_ferry: [ + { + name: "Π‘ΠΈΠ»Π΅Ρ‚ Π½Π° ΠΏΠ°Ρ€ΠΎΠΌ/ΠΊΡ€ΡƒΠΈΠ·", + field: "ferry_ticket", + file_type: "ferry_ticket", + required: true, + maxFiles: 1, + description: "Π‘ΠΈΠ»Π΅Ρ‚ ΠΈΠ»ΠΈ booking confirmation" + }, + { + name: "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΈ/ΠΎΡ‚ΠΌΠ΅Π½Ρ‹", + field: "delay_proof", + file_type: "ferry_delay_proof", + required: true, + maxFiles: 3, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΎΡ‚ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ·Ρ‡ΠΈΠΊΠ°, Ρ„ΠΎΡ‚ΠΎ расписания, email/SMS" + } + ], + + emergency_landing: [ + { + name: "ΠŸΠΎΡΠ°Π΄ΠΎΡ‡Π½Ρ‹ΠΉ Ρ‚Π°Π»ΠΎΠ½ ΠΈΠ»ΠΈ Π‘ΠΈΠ»Π΅Ρ‚", + field: "boarding_or_ticket", + file_type: "emergency_boarding_or_ticket", + required: true, + maxFiles: 1, + description: "Boarding pass ΠΈΠ»ΠΈ ticket" + }, + { + name: "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π΅Π½ΠΈΠ΅ посадки Π½Π° запасной аэродром", + field: "emergency_proof", + file_type: "emergency_landing_proof", + required: true, + maxFiles: 3, + description: "Π‘ΠΏΡ€Π°Π²ΠΊΠ° ΠΎΡ‚ АК, email/SMS, Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹" + } + ] +}; + export default function Step2Details({ formData, updateFormData, onNext, onPrev, addDebugEvent }: Props) { const [form] = Form.useForm(); - const [fileList, setFileList] = useState([]); + const [eventType, setEventType] = useState(formData.eventType || ''); + const [documentFiles, setDocumentFiles] = useState>({}); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(''); + const [waitingForOcr, setWaitingForOcr] = useState(false); + const [ocrResults, setOcrResults] = useState(null); + const eventSourceRef = useRef(null); + + const handleEventTypeChange = (value: string) => { + setEventType(value); + setDocumentFiles({}); // ΠžΡ‡ΠΈΡ‰Π°Π΅ΠΌ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ ΠΏΡ€ΠΈ смСнС Ρ‚ΠΈΠΏΠ° + form.setFieldValue('eventType', value); + }; + + // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² для Π²Ρ‹Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° события + const currentDocuments = eventType ? DOCUMENT_CONFIGS[eventType] || [] : []; + + const handleUploadChange = (field: string, { fileList: newFileList }: any) => { + setDocumentFiles(prev => ({ + ...prev, + [field]: newFileList + })); + }; const handleNext = async () => { try { const values = await form.validateFields(); - // Если Π΅ΡΡ‚ΡŒ Ρ„Π°ΠΉΠ»Ρ‹ - Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ - if (fileList.length > 0) { - setUploading(true); - setUploadProgress('πŸ“€ ΠŸΠΎΠ΄Π³ΠΎΡ‚Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹...'); - - addDebugEvent?.('upload', 'pending', `πŸ“€ Π—Π°Π³Ρ€ΡƒΠΆΠ°ΡŽ ${fileList.length} Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚(ΠΎΠ²) Π² S3 Ρ‡Π΅Ρ€Π΅Π· n8n...`, { - count: fileList.length - }); + // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ Ρ‡Ρ‚ΠΎ всС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Ρ‹ + const missingDocs = currentDocuments.filter(doc => + doc.required && (!documentFiles[doc.field] || documentFiles[doc.field].length === 0) + ); + + if (missingDocs.length > 0) { + message.error(`Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚Π΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹: ${missingDocs.map(d => d.name).join(', ')}`); + return; + } - // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ claim_id ΠΈΠ· formData (ΡƒΠΆΠ΅ сгСнСрирован Π² Step1) - const claimId = formData.claim_id; + // Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ всС Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ Π² S3 Ρ‡Π΅Ρ€Π΅Π· n8n + setUploading(true); + setUploadProgress('πŸ“€ Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹...'); + + const claimId = formData.claim_id; + const uploadedFiles: any[] = []; - // Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ Ρ‡Π΅Ρ€Π΅Π· n8n Π²Π΅Π±Ρ…ΡƒΠΊ - const uploadedFiles = []; + for (const docConfig of currentDocuments) { + const files = documentFiles[docConfig.field] || []; - for (let i = 0; i < fileList.length; i++) { - const file = fileList[i]; + for (let i = 0; i < files.length; i++) { + const file = files[i]; if (!file.originFileObj) continue; - setUploadProgress(`πŸ“‘ Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ ${i + 1} ΠΈΠ· ${fileList.length}: ${file.name}...`); + setUploadProgress(`πŸ“‘ Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ: ${docConfig.name} (${i + 1}/${files.length})...`); const uploadFormData = new FormData(); uploadFormData.append('claim_id', claimId); - uploadFormData.append('file_type', `document_${i + 1}`); // document_1, document_2, etc + uploadFormData.append('file_type', docConfig.file_type); // πŸ”‘ Π£Π½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ file_type для n8n uploadFormData.append('filename', file.name); uploadFormData.append('voucher', formData.voucher || ''); uploadFormData.append('session_id', sessionStorage.getItem('session_id') || 'unknown'); uploadFormData.append('upload_timestamp', new Date().toISOString()); uploadFormData.append('file', file.originFileObj); + addDebugEvent?.('upload', 'pending', `πŸ“€ Π—Π°Π³Ρ€ΡƒΠΆΠ°ΡŽ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚: ${docConfig.name} (${docConfig.file_type})`, { + file_type: docConfig.file_type, + filename: file.name + }); + const uploadResponse = await fetch('https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95', { method: 'POST', body: uploadFormData, }); - setUploadProgress(`πŸ” ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ ${i + 1} ΠΈΠ· ${fileList.length}...`); const uploadResult = await uploadResponse.json(); - const resultData = Array.isArray(uploadResult) ? uploadResult[0] : uploadResult; + if (resultData?.success) { uploadedFiles.push({ filename: file.name, + file_type: docConfig.file_type, + field: docConfig.field, success: true }); + + addDebugEvent?.('upload', 'success', `βœ… Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½: ${docConfig.name}`, { + file_type: docConfig.file_type, + filename: file.name + }); } } + } - const uploadResult = { - success: uploadedFiles.length > 0, - uploaded_count: uploadedFiles.length, - total_count: fileList.length, - files: uploadedFiles - }; - - if (uploadResult.success) { - addDebugEvent?.('upload', 'success', `βœ… Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Ρ‹ Ρ‡Π΅Ρ€Π΅Π· n8n: ${uploadResult.uploaded_count}/${uploadResult.total_count}`, { - files: uploadResult.files, - claim_id: claimId - }); - - updateFormData({ - ...values, - uploadedFiles: uploadResult.files - }); - } else { - message.error('Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²'); - setUploading(false); - setUploadProgress(''); - return; - } + if (uploadedFiles.length > 0) { + setUploadProgress('πŸ€– AI Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹...'); + + updateFormData({ + ...values, + uploadedDocuments: uploadedFiles + }); + // TODO: Π—Π΄Π΅ΡΡŒ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΠ΅ SSE события с Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°ΠΌΠΈ OCR/AI + // Пока просто ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ дальшС + setUploadProgress(''); + setUploading(false); + + message.success(`Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²: ${uploadedFiles.length}. ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ дальшС...`); + onNext(); + } else { + message.error('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹'); setUploading(false); setUploadProgress(''); - } else { - updateFormData(values); } - onNext(); } catch (error) { message.error('Π—Π°ΠΏΠΎΠ»Π½ΠΈΡ‚Π΅ всС ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ поля'); setUploading(false); @@ -120,21 +291,6 @@ export default function Step2Details({ formData, updateFormData, onNext, onPrev, } }; - const handleUploadChange = ({ fileList: newFileList }: any) => { - setFileList(newFileList); - }; - - const [eventType, setEventType] = useState(formData.eventType || ''); - - const handleEventTypeChange = (value: string) => { - setEventType(value); - form.setFieldValue('eventType', value); - }; - - // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ Π½ΡƒΠΆΠ½Ρ‹ Π»ΠΈ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ поля для стыковочного рСйса - const showConnectionFields = eventType === 'miss_connection'; - const showCancelFlightDocs = eventType === 'cancel_flight'; - return (
- - current && current > dayjs().endOf('day')} - /> - - - {/* Для стыковочного рСйса - Π½ΠΎΠΌΠ΅Ρ€ рСйса прибытия */} - {showConnectionFields && ( - 0 && ( + - - - )} +
+

+ πŸ’‘ ΠŸΡ€ΠΎΡΡ‚ΠΎ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ β€” наш AI автоматичСски распознаСт всС Π΄Π°Π½Π½Ρ‹Π΅ + (Π½ΠΎΠΌΠ΅Ρ€Π° рСйсов, Π΄Π°Ρ‚Ρ‹, врСмя, ΠΏΡ€ΠΈΡ‡ΠΈΠ½Ρ‹ Π·Π°Π΄Π΅Ρ€ΠΆΠ΅ΠΊ) +

+
- {showConnectionFields && ( - - current && current > dayjs().endOf('day')} - /> - - )} - - {/* Для стыковочного рСйса - Π½ΠΎΠΌΠ΅Ρ€ рСйса отправлСния */} - {showConnectionFields && ( - - - - )} - - {showConnectionFields && ( - - current && current > dayjs().endOf('day')} - /> - - )} - - {/* Для ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Ρ… рСйсов */} - {!showConnectionFields && ( - - - - )} - - {/* Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ для ΠΎΡ‚ΠΌΠ΅Π½Ρ‹ рСйса */} - {showCancelFlightDocs && ( - - { - const isLt15M = file.size / 1024 / 1024 < 15; - if (!isLt15M) { - message.error(`${file.name}: Ρ„Π°ΠΉΠ» большС 15MB`); - return Upload.LIST_IGNORE; - } + {currentDocuments.map((doc, index) => ( +
+
+ + {doc.required ? 'βœ…' : 'ℹ️'} {doc.name} + {doc.required && *} + +
- const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'application/pdf']; - const validExtensions = /\.(jpg|jpeg|png|pdf|heic|heif|webp)$/i; - - if (!validTypes.includes(file.type) && !validExtensions.test(file.name)) { - message.error(`${file.name}: Π½Π΅ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹ΠΉ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚`); - return Upload.LIST_IGNORE; - } - - return false; - }} - accept="image/*,.pdf,.heic,.heif,.webp" - multiple - maxCount={5} - > - - - - )} +
+ πŸ’‘ {doc.description} +
- - { - 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; - } - - const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'application/pdf']; - const validExtensions = /\.(jpg|jpeg|png|pdf|heic|heif|webp)$/i; - - if (!validTypes.includes(file.type) && !validExtensions.test(file.name)) { - message.error(`${file.name}: Π½Π΅ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹ΠΉ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚`); - return Upload.LIST_IGNORE; - } - - return false; - }} - accept="image/*,.pdf,.heic,.heif,.webp" - multiple - maxCount={10} - showUploadList={{ - showPreviewIcon: true, - showRemoveIcon: true, - }} - > - - -
- Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {fileList.length}/10 Ρ„Π°ΠΉΠ»ΠΎΠ² -
-
+ handleUploadChange(doc.field, info)} + beforeUpload={(file) => { + const isLt15M = file.size / 1024 / 1024 < 15; + if (!isLt15M) { + message.error(`${file.name}: Ρ„Π°ΠΉΠ» большС 15MB`); + return Upload.LIST_IGNORE; + } + + const currentFiles = documentFiles[doc.field] || []; + if (currentFiles.length >= doc.maxFiles) { + message.error(`ΠœΠ°ΠΊΡΠΈΠΌΡƒΠΌ ${doc.maxFiles} Ρ„Π°ΠΉΠ»(ΠΎΠ²) для этого Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°`); + return Upload.LIST_IGNORE; + } + + const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'application/pdf']; + const validExtensions = /\.(jpg|jpeg|png|pdf|heic|heif|webp)$/i; + + if (!validTypes.includes(file.type) && !validExtensions.test(file.name)) { + message.error(`${file.name}: Π½Π΅ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹ΠΉ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚`); + return Upload.LIST_IGNORE; + } + + return false; + }} + accept="image/*,.pdf,.heic,.heif,.webp" + multiple={doc.maxFiles > 1} + maxCount={doc.maxFiles} + showUploadList={{ + showPreviewIcon: true, + showRemoveIcon: true, + }} + > + + + +
+ Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ΠΎ: {(documentFiles[doc.field] || []).length}/{doc.maxFiles} Ρ„Π°ΠΉΠ»(ΠΎΠ²) +
+
+ ))} + + )} {/* ΠŸΡ€ΠΎΠ³Ρ€Π΅ΡΡ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ */} {uploading && uploadProgress && ( @@ -384,8 +447,6 @@ export default function Step2Details({ formData, updateFormData, onNext, onPrev, // ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ, заполняСм ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ const devData = { eventType: 'delay_flight', - incidentDate: dayjs(), - transportNumber: 'TEST123', }; updateFormData(devData); onNext();