backup: Сохранён старый Step2Details с ручным вводом полей

Создана копия Step2Details.OLD_MANUAL_INPUT.tsx перед переходом на
умную форму с автоматическим распознаванием документов через OCR/AI.

В случае необходимости можно вернуться к ручному вводу.
This commit is contained in:
AI Assistant
2025-10-28 11:32:07 +03:00
parent e26ec2941a
commit 6fe14598a1

View File

@@ -0,0 +1,402 @@
import { Form, Input, Button, Select, DatePicker, Upload, message, Spin, Alert } from 'antd';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
import { useState } from 'react';
import type { UploadFile } from 'antd/es/upload/interface';
import dayjs from 'dayjs';
const { Option } = Select;
interface Props {
formData: any;
updateFormData: (data: any) => void;
onNext: () => void;
onPrev: () => void;
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: '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<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState('');
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
});
// Используем claim_id из formData (уже сгенерирован в Step1)
const claimId = formData.claim_id;
// Загружаем каждый документ через n8n вебхук
const uploadedFiles = [];
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
if (!file.originFileObj) continue;
setUploadProgress(`📡 Загружаем документ ${i + 1} из ${fileList.length}: ${file.name}...`);
const uploadFormData = new FormData();
uploadFormData.append('claim_id', claimId);
uploadFormData.append('file_type', `document_${i + 1}`); // document_1, document_2, etc
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);
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,
success: true
});
}
}
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;
}
setUploading(false);
setUploadProgress('');
} else {
updateFormData(values);
}
onNext();
} catch (error) {
message.error('Заполните все обязательные поля');
setUploading(false);
setUploadProgress('');
}
};
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 (
<Form
form={form}
layout="vertical"
initialValues={formData}
style={{ marginTop: 24 }}
>
<Form.Item
label="Выберите тип события"
name="eventType"
rules={[{ required: true, message: 'Выберите тип события' }]}
>
<Select
placeholder="Выберите тип события"
size="large"
onChange={handleEventTypeChange}
>
{EVENT_TYPES.map(type => (
<Option key={type.value} value={type.value}>
{type.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="Дата наступления страхового случая"
name="incidentDate"
rules={[{ required: true, message: 'Укажите дату' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
{/* Для стыковочного рейса - номер рейса прибытия */}
{showConnectionFields && (
<Form.Item
label="Укажите номер рейса прибытия"
name="arrivalFlightNumber"
rules={[{ required: true, message: 'Введите номер рейса прибытия' }]}
>
<Input
placeholder="Введите номер"
size="large"
/>
</Form.Item>
)}
{showConnectionFields && (
<Form.Item
label="Дата рейса прибытия"
name="arrivalFlightDate"
rules={[{ required: true, message: 'Укажите дату прибытия' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
)}
{/* Для стыковочного рейса - номер рейса отправления */}
{showConnectionFields && (
<Form.Item
label="Укажите номер рейса отправления"
name="departureFlightNumber"
rules={[{ required: true, message: 'Введите номер рейса отправления' }]}
>
<Input
placeholder="Введите номер рейса отправления"
size="large"
/>
</Form.Item>
)}
{showConnectionFields && (
<Form.Item
label="Дата рейса отправления"
name="departureFlightDate"
rules={[{ required: true, message: 'Укажите дату отправления' }]}
>
<DatePicker
placeholder="Выберите дату"
size="large"
style={{ width: '100%' }}
format="DD.MM.YYYY"
disabledDate={(current) => current && current > dayjs().endOf('day')}
/>
</Form.Item>
)}
{/* Для обычных рейсов */}
{!showConnectionFields && (
<Form.Item
label="Номер рейса/поезда/парома"
name="transportNumber"
rules={[{ required: true, message: 'Введите номер' }]}
>
<Input
placeholder="Введите номер"
size="large"
/>
</Form.Item>
)}
{/* Дополнительные документы для отмены рейса */}
{showCancelFlightDocs && (
<Form.Item
label="Подтверждение уведомления об отмене рейса от АК"
name="cancelConfirmation"
tooltip="Уведомление от авиакомпании об отмене"
>
<Upload
listType="picture"
beforeUpload={(file) => {
const isLt15M = file.size / 1024 / 1024 < 15;
if (!isLt15M) {
message.error(`${file.name}: файл больше 15MB`);
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={5}
>
<Button icon={<UploadOutlined />} size="large" block>
Загрузить подтверждение отмены
</Button>
</Upload>
</Form.Item>
)}
<Form.Item
label="Подтверждающие документы"
name="documents"
tooltip="Посадочный талон, билет, справка о задержке и т.д."
>
<Upload
listType="picture"
fileList={fileList}
onChange={handleUploadChange}
beforeUpload={(file) => {
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,
}}
>
<Button icon={<UploadOutlined />} size="large" block disabled={fileList.length >= 10}>
Загрузить файлы (до 10 шт, макс 15MB каждый)
</Button>
</Upload>
<div style={{ marginTop: 8, fontSize: 12, color: '#999' }}>
Загружено: {fileList.length}/10 файлов
</div>
</Form.Item>
{/* Прогресс обработки */}
{uploading && uploadProgress && (
<Alert
message={uploadProgress}
type="info"
showIcon
icon={<Spin indicator={<LoadingOutlined style={{ fontSize: 16 }} spin />} />}
style={{ marginBottom: 16, marginTop: 16 }}
/>
)}
<Form.Item>
<div style={{ display: 'flex', gap: 8, marginTop: 32 }}>
<Button onClick={onPrev} size="large" disabled={uploading}>Назад</Button>
<Button
type="primary"
onClick={handleNext}
loading={uploading}
style={{ flex: 1 }}
size="large"
>
{uploading ? 'Обрабатываем...' : 'Далее'}
</Button>
</div>
</Form.Item>
{/* 🔧 Технические кнопки для разработки */}
<div style={{
marginTop: 24,
padding: 16,
background: '#f0f0f0',
borderRadius: 8,
border: '2px dashed #999'
}}>
<div style={{ marginBottom: 8, fontSize: 12, color: '#666', fontWeight: 'bold' }}>
🔧 DEV MODE - Быстрая навигация (без валидации)
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button
onClick={onPrev}
size="small"
disabled={uploading}
>
Назад (Step 1)
</Button>
<Button
type="dashed"
onClick={() => {
// Пропускаем валидацию, заполняем минимальные данные
const devData = {
eventType: 'delay_flight',
incidentDate: dayjs(),
transportNumber: 'TEST123',
};
updateFormData(devData);
onNext();
}}
size="small"
style={{ flex: 1 }}
>
Далее (Step 3) [пропустить]
</Button>
</div>
</div>
</Form>
);
}