feat: 6 улучшений формы - S3 upload, draft, HEIC, email на step3

1.  Placeholder с тире E1000-302538524
   - Теперь в placeholder тоже тире

2.  Email перенесен на Step3
   - Убран с Step1 (проверка полиса)
   - Добавлен на Step3 (вместе с телефоном)
   - Теперь телефон + email + выплата на одном шаге

3.  HEIC формат + мультилоад
   - Добавлена поддержка .heic, .heif (iPhone формат)
   - Убран maxCount - неограниченная загрузка
   - Параметр multiple для множественной загрузки

4.  S3 Upload
   - Создан s3_service.py для работы с Timeweb S3
   - Новый endpoint: POST /api/v1/upload/files
   - Поддержка мультизагрузки файлов
   - Автоматическая генерация уникальных имен
   - Файлы грузятся в S3, не локально

5.  Draft автосохранение
   - Создана таблица claims_draft в PostgreSQL
   - Новый API: POST /api/v1/draft/save
   - GET /api/v1/draft/stats - статистика по шагам
   - GET /api/v1/draft/list - список последних драфтов
   - Для аналитики: где люди бросают заполнение

6.  Миграция БД
   - 002_create_claims_draft.sql применена
   - Индексы для быстрого поиска
   - JSONB поле для гибкости данных

Backend:
- s3_service.py - сервис для S3
- draft.py - API автосохранения
- upload.py - обновлен endpoint для S3
- main.py - добавлены роуты и подключения

Frontend:
- Step1Policy: убран email, добавлен HEIC
- Step3Payment: добавлен email после телефона

Статус:  Backend подключен к S3, таблица создана, всё работает
This commit is contained in:
AI Assistant
2025-10-24 21:24:00 +03:00
parent f2cfa54c9d
commit e34f7a598b
7 changed files with 420 additions and 26 deletions

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { Form, Input, Button, message, Upload } from 'antd';
import { FileProtectOutlined, MailOutlined, UploadOutlined } from '@ant-design/icons';
import { FileProtectOutlined, UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload/interface';
interface Props {
@@ -71,7 +71,7 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
const checkPolicy = async () => {
try {
const values = await form.validateFields(['voucher', 'email']);
const values = await form.validateFields(['voucher']);
setLoading(true);
setPolicyNotFound(false);
@@ -82,7 +82,7 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
voucher: values.voucher,
email: values.email,
email: 'temp@check.com', // Email не требуется на этом шаге
}),
});
@@ -154,7 +154,7 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
>
<Input
prefix={<FileProtectOutlined />}
placeholder="E1000302538524"
placeholder="E1000-302538524"
size="large"
onChange={handleVoucherChange}
onPaste={handleVoucherPaste}
@@ -162,22 +162,6 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
/>
</Form.Item>
<Form.Item
label="Электронная почта"
name="email"
rules={[
{ required: true, message: 'Введите email' },
{ type: 'email', message: 'Неверный формат email' }
]}
>
<Input
prefix={<MailOutlined />}
placeholder="example@mail.ru"
size="large"
type="email"
/>
</Form.Item>
{!policyNotFound && (
<Form.Item>
<Button
@@ -219,11 +203,11 @@ export default function Step1Policy({ formData, updateFormData, onNext }: Props)
fileList={fileList}
onChange={handleUploadChange}
beforeUpload={() => false}
accept="image/*,.pdf"
maxCount={3}
accept="image/*,.pdf,.heic,.heif"
multiple
>
<Button icon={<UploadOutlined />} size="large" block>
Выбрать файл (фото или PDF)
Выбрать файлы (фото, PDF, HEIC)
</Button>
</Upload>
</Form.Item>

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined } from '@ant-design/icons';
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined } from '@ant-design/icons';
const { Option } = Select;
@@ -139,6 +139,23 @@ export default function Step3Payment({
/>
</Form.Item>
<Form.Item
label="Электронная почта"
name="email"
rules={[
{ required: true, message: 'Введите email' },
{ type: 'email', message: 'Неверный формат email' }
]}
>
<Input
prefix={<MailOutlined />}
placeholder="example@mail.ru"
size="large"
type="email"
disabled={isPhoneVerified}
/>
</Form.Item>
{!isPhoneVerified && (
<>
<Form.Item>